├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── data ├── 1.jpg ├── 10.jpg ├── 11.png ├── 12.gif ├── 2.png ├── 3.gif ├── 4.jpg ├── 5.png ├── 6.gif ├── 7.jpg ├── 8.png └── 9.gif ├── expected ├── imgsmlr.out └── imgsmlr_1.out ├── imgsmlr--1.0.sql ├── imgsmlr.c ├── imgsmlr.control ├── imgsmlr.h ├── imgsmlr_idx.c ├── sql └── imgsmlr.sql └── travis ├── dep-ubuntu-llvm.sh ├── dep-ubuntu-postgres.sh ├── llvm-snapshot.gpg.key ├── pg-travis-test.sh └── postgresql.gpg.key /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.so 3 | *.o 4 | *.hex 5 | regression.diffs 6 | regression.out 7 | results 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | sudo: required 5 | dist: trusty 6 | 7 | language: c 8 | 9 | compiler: 10 | - clang 11 | - gcc 12 | 13 | before_install: 14 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -y install -qq wget ca-certificates; fi 15 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then source ./travis/dep-ubuntu-postgres.sh; fi 16 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then source ./travis/dep-ubuntu-llvm.sh; fi 17 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq; fi 18 | 19 | env: 20 | global: 21 | - LLVM_VER=4.0 22 | matrix: 23 | - PG_VER=10 CHECK_TYPE=normal 24 | - PG_VER=10 CHECK_TYPE=static 25 | - PG_VER=10 CHECK_TYPE=valgrind 26 | - PG_VER=9.6 CHECK_TYPE=normal 27 | - PG_VER=9.6 CHECK_TYPE=static 28 | - PG_VER=9.5 CHECK_TYPE=normal 29 | - PG_VER=9.5 CHECK_TYPE=static 30 | 31 | script: bash ./travis/pg-travis-test.sh 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | imgsmlr is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. 2 | 3 | Copyright (c) 2015-2018, Postgres Professional 4 | Copyright (c) 2013-2015, Alexander Korotkov 5 | Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group 6 | Portions Copyright (c) 1994, The Regents of the University of California 7 | 8 | Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. 9 | 10 | IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | 12 | POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # imgsmlr/Makefile 2 | 3 | MODULE_big = imgsmlr 4 | OBJS = imgsmlr.o imgsmlr_idx.o 5 | EXTENSION = imgsmlr 6 | DATA = imgsmlr--1.0.sql 7 | SHLIB_LINK = -lgd 8 | REGRESS = imgsmlr 9 | EXTRA_CLEAN = data/*.hex 10 | 11 | 12 | ifdef USE_PGXS 13 | PG_CONFIG = pg_config 14 | PGXS := $(shell $(PG_CONFIG) --pgxs) 15 | include $(PGXS) 16 | else 17 | subdir = contrib/imgsmlr 18 | top_builddir = ../.. 19 | include $(top_builddir)/src/Makefile.global 20 | include $(top_srcdir)/contrib/contrib-global.mk 21 | endif 22 | 23 | ifdef USE_ASSERT_CHECKING 24 | override CFLAGS += -DUSE_ASSERT_CHECKING 25 | endif 26 | 27 | installcheck: data/1.jpg.hex data/2.png.hex data/3.gif.hex data/4.jpg.hex data/5.png.hex data/6.gif.hex data/7.jpg.hex data/8.png.hex data/9.gif.hex data/10.jpg.hex data/11.png.hex data/12.gif.hex 28 | 29 | data/%.hex: data/% 30 | xxd -p $< > $@ 31 | 32 | maintainer-clean: 33 | rm -f data/*.hex -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/postgrespro/imgsmlr.svg?branch=master)](https://travis-ci.org/postgrespro/imgsmlr) 2 | [![codecov](https://codecov.io/gh/postgrespro/imgsmlr/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/imgsmlr) 3 | [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/imgsmlr/master/LICENSE) 4 | 5 | ImgSmlr – similar images search for PostgreSQL 6 | ============================================== 7 | 8 | Introduction 9 | ------------ 10 | 11 | ImgSmlr – is a PostgreSQL extension which implements similar images searching 12 | functionality. 13 | 14 | ImgSmlr method is based on Haar wavelet transform. The goal of ImgSmlr is not 15 | to provide most advanced state of art similar images searching methods. ImgSmlr 16 | was written as sample extension which illustrate how PostgreSQL extendability 17 | could cover such untypical tasks for RDBMS as similar images search. 18 | 19 | Authors 20 | ------- 21 | 22 | * Alexander Korotkov , Postgres Professional, Moscow, Russia 23 | 24 | Availability 25 | ------------ 26 | 27 | ImgSmlr is released as an extension and not available in default PostgreSQL 28 | installation. It is available from 29 | [github](https://github.com/postgrespro/imgsmlr) 30 | under the same license as 31 | [PostgreSQL](http://www.postgresql.org/about/licence/) 32 | and supports PostgreSQL 9.1+. 33 | 34 | Installation 35 | ------------ 36 | 37 | Before build and install ImgSmlr you should ensure following: 38 | 39 | * PostgreSQL version is 9.1 or higher. 40 | * You have development package of PostgreSQL installed or you built 41 | PostgreSQL from source. 42 | * You have gd2 library installed on your system. 43 | * Your PATH variable is configured so that pg\_config command available. 44 | 45 | Typical installation procedure may look like this: 46 | 47 | $ git clone https://github.com/postgrespro/imgsmlr.git 48 | $ cd imgsmlr 49 | $ make USE_PGXS=1 50 | $ sudo make USE_PGXS=1 install 51 | $ make USE_PGXS=1 installcheck 52 | $ psql DB -c "CREATE EXTENSION imgsmlr;" 53 | 54 | Usage 55 | ----- 56 | 57 | ImgSmlr offers two datatypes: pattern and signature. 58 | 59 | | Datatype | Storage length | Description | 60 | | --------- |--------------: | ------------------------------------------------------------------ | 61 | | pattern | 16388 bytes | Result of Haar wavelet transform on the image | 62 | | signature | 64 bytes | Short representation of pattern for fast search using GiST indexes | 63 | 64 | There is set of functions *2pattern(bytea) which converts bynary data in given format into pattern. Convertion into pattern consists of following steps. 65 | 66 | * Decompress image. 67 | * Make image black&white. 68 | * Resize image to 64x64 pixels. 69 | * Apply Haar wavelet transform to the image. 70 | 71 | Pattern could be converted into signature and shuffled for less sensitivity to image shift. 72 | 73 | | Function | Return type | Description | 74 | | -------------------------- |-------------| --------------------------------------------------- | 75 | | jpeg2pattern(bytea) | pattern | Convert jpeg image into pattern | 76 | | png2pattern(bytea) | pattern | Convert png image into pattern | 77 | | gif2pattern(bytea) | pattern | Convert gif image into pattern | 78 | | pattern2signature(pattern) | signature | Create signature from pattern | 79 | | shuffle_pattern(pattern) | pattern | Shuffle pattern for less sensitivity to image shift | 80 | 81 | Both pattern and signature datatypes supports `<->` operator for eucledian distance. Signature also supports GiST indexing with KNN on `<->` operator. 82 | 83 | | Operator | Left type | Right type | Return type | Description | 84 | | -------- |-----------| ---------- | ----------- | ----------------------------------------- | 85 | | <-> | pattern | pattern | float8 | Eucledian distance between two patterns | 86 | | <-> | signature | signature | float8 | Eucledian distance between two signatures | 87 | 88 | The idea is to find top N similar images by signature using GiST index. Then find top n (n < N) similar images by pattern from top N similar images by signature. 89 | 90 | Example 91 | ------- 92 | 93 | Let us assume we have an `image` table with columns `id` and `data` where `data` column contains binary jpeg data. We can create `pat` table with patterns and signatures of given images using following query. 94 | 95 | ```sql 96 | CREATE TABLE pat AS ( 97 | SELECT 98 | id, 99 | shuffle_pattern(pattern) AS pattern, 100 | pattern2signature(pattern) AS signature 101 | FROM ( 102 | SELECT 103 | id, 104 | jpeg2pattern(data) AS pattern 105 | FROM 106 | image 107 | ) x 108 | ); 109 | ``` 110 | 111 | Then let's create primary key for `pat` table and GiST index for signatures. 112 | 113 | ```sql 114 | ALTER TABLE pat ADD PRIMARY KEY (id); 115 | CREATE INDEX pat_signature_idx ON pat USING gist (signature); 116 | ``` 117 | 118 | Prelimimary work is done. Now we can search for top 10 similar images to given image with specified id using following query. 119 | 120 | ```sql 121 | SELECT 122 | id, 123 | smlr 124 | FROM 125 | ( 126 | SELECT 127 | id, 128 | pattern <-> (SELECT pattern FROM pat WHERE id = :id) AS smlr 129 | FROM pat 130 | WHERE id <> :id 131 | ORDER BY 132 | signature <-> (SELECT signature FROM pat WHERE id = :id) 133 | LIMIT 100 134 | ) x 135 | ORDER BY x.smlr ASC 136 | LIMIT 10 137 | ``` 138 | 139 | Inner query selects top 100 images by signature using GiST index. Outer query search for top 10 images by pattern from images found by inner query. You can adjust both of number to achieve better search results on your images collection. 140 | -------------------------------------------------------------------------------- /data/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/1.jpg -------------------------------------------------------------------------------- /data/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/10.jpg -------------------------------------------------------------------------------- /data/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/11.png -------------------------------------------------------------------------------- /data/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/12.gif -------------------------------------------------------------------------------- /data/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/2.png -------------------------------------------------------------------------------- /data/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/3.gif -------------------------------------------------------------------------------- /data/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/4.jpg -------------------------------------------------------------------------------- /data/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/5.png -------------------------------------------------------------------------------- /data/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/6.gif -------------------------------------------------------------------------------- /data/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/7.jpg -------------------------------------------------------------------------------- /data/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/8.png -------------------------------------------------------------------------------- /data/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postgrespro/imgsmlr/c484a15cc4ad254b0cc6cd7dc4820ad6472fcfac/data/9.gif -------------------------------------------------------------------------------- /expected/imgsmlr.out: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION imgsmlr; 2 | CREATE TABLE image (id integer PRIMARY KEY, data bytea); 3 | CREATE TABLE tmp (data text); 4 | \copy tmp from 'data/1.jpg.hex' 5 | INSERT INTO image VALUES (1, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 6 | TRUNCATE tmp; 7 | \copy tmp from 'data/2.png.hex' 8 | INSERT INTO image VALUES (2, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 9 | TRUNCATE tmp; 10 | \copy tmp from 'data/3.gif.hex' 11 | INSERT INTO image VALUES (3, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 12 | TRUNCATE tmp; 13 | \copy tmp from 'data/4.jpg.hex' 14 | INSERT INTO image VALUES (4, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 15 | TRUNCATE tmp; 16 | \copy tmp from 'data/5.png.hex' 17 | INSERT INTO image VALUES (5, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 18 | TRUNCATE tmp; 19 | \copy tmp from 'data/6.gif.hex' 20 | INSERT INTO image VALUES (6, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 21 | TRUNCATE tmp; 22 | \copy tmp from 'data/7.jpg.hex' 23 | INSERT INTO image VALUES (7, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 24 | TRUNCATE tmp; 25 | \copy tmp from 'data/8.png.hex' 26 | INSERT INTO image VALUES (8, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 27 | TRUNCATE tmp; 28 | \copy tmp from 'data/9.gif.hex' 29 | INSERT INTO image VALUES (9, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 30 | TRUNCATE tmp; 31 | \copy tmp from 'data/10.jpg.hex' 32 | INSERT INTO image VALUES (10, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 33 | TRUNCATE tmp; 34 | \copy tmp from 'data/11.png.hex' 35 | INSERT INTO image VALUES (11, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 36 | TRUNCATE tmp; 37 | \copy tmp from 'data/12.gif.hex' 38 | INSERT INTO image VALUES (12, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 39 | TRUNCATE tmp; 40 | CREATE TABLE pat AS ( 41 | SELECT 42 | id, 43 | shuffle_pattern(pattern)::text::pattern AS pattern, 44 | pattern2signature(pattern)::text::signature AS signature 45 | FROM ( 46 | SELECT 47 | id, 48 | (CASE WHEN id % 3 = 1 THEN jpeg2pattern(data) 49 | WHEN id % 3 = 2 THEN png2pattern(data) 50 | WHEN id % 3 = 0 THEN gif2pattern(data) 51 | ELSE NULL END) AS pattern 52 | FROM 53 | image 54 | ) x 55 | ); 56 | ALTER TABLE pat ADD PRIMARY KEY (id); 57 | CREATE INDEX pat_signature_idx ON pat USING gist (signature); 58 | SELECT p1.id, p2.id, round((p1.pattern <-> p2.pattern)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id; 59 | id | id | round 60 | ----+----+-------- 61 | 1 | 1 | 0.0000 62 | 1 | 2 | 1.5411 63 | 1 | 3 | 1.6179 64 | 1 | 4 | 2.8484 65 | 1 | 5 | 3.1757 66 | 1 | 6 | 2.7149 67 | 1 | 7 | 3.7669 68 | 1 | 8 | 3.8673 69 | 1 | 9 | 3.8833 70 | 1 | 10 | 4.7472 71 | 1 | 11 | 4.3779 72 | 1 | 12 | 3.5497 73 | 2 | 1 | 1.5411 74 | 2 | 2 | 0.0000 75 | 2 | 3 | 1.2575 76 | 2 | 4 | 2.7384 77 | 2 | 5 | 2.8913 78 | 2 | 6 | 2.6645 79 | 2 | 7 | 3.7849 80 | 2 | 8 | 3.8382 81 | 2 | 9 | 3.8564 82 | 2 | 10 | 4.9827 83 | 2 | 11 | 4.5090 84 | 2 | 12 | 3.7336 85 | 3 | 1 | 1.6179 86 | 3 | 2 | 1.2575 87 | 3 | 3 | 0.0000 88 | 3 | 4 | 2.7808 89 | 3 | 5 | 3.0869 90 | 3 | 6 | 2.7949 91 | 3 | 7 | 3.8342 92 | 3 | 8 | 3.9121 93 | 3 | 9 | 3.9258 94 | 3 | 10 | 4.8348 95 | 3 | 11 | 4.4050 96 | 3 | 12 | 3.6224 97 | 4 | 1 | 2.8484 98 | 4 | 2 | 2.7384 99 | 4 | 3 | 2.7808 100 | 4 | 4 | 0.0000 101 | 4 | 5 | 2.4249 102 | 4 | 6 | 2.0933 103 | 4 | 7 | 3.7250 104 | 4 | 8 | 3.6681 105 | 4 | 9 | 3.6846 106 | 4 | 10 | 5.0549 107 | 4 | 11 | 4.6132 108 | 4 | 12 | 3.8741 109 | 5 | 1 | 3.1757 110 | 5 | 2 | 2.8913 111 | 5 | 3 | 3.0869 112 | 5 | 4 | 2.4249 113 | 5 | 5 | 0.0000 114 | 5 | 6 | 2.9383 115 | 5 | 7 | 3.5100 116 | 5 | 8 | 3.4645 117 | 5 | 9 | 3.4829 118 | 5 | 10 | 5.1797 119 | 5 | 11 | 4.7246 120 | 5 | 12 | 3.8624 121 | 6 | 1 | 2.7149 122 | 6 | 2 | 2.6645 123 | 6 | 3 | 2.7949 124 | 6 | 4 | 2.0933 125 | 6 | 5 | 2.9383 126 | 6 | 6 | 0.0000 127 | 6 | 7 | 3.6537 128 | 6 | 8 | 3.7144 129 | 6 | 9 | 3.7347 130 | 6 | 10 | 5.1326 131 | 6 | 11 | 4.5488 132 | 6 | 12 | 3.9115 133 | 7 | 1 | 3.7669 134 | 7 | 2 | 3.7849 135 | 7 | 3 | 3.8342 136 | 7 | 4 | 3.7250 137 | 7 | 5 | 3.5100 138 | 7 | 6 | 3.6537 139 | 7 | 7 | 0.0000 140 | 7 | 8 | 1.2442 141 | 7 | 9 | 1.2549 142 | 7 | 10 | 5.0754 143 | 7 | 11 | 4.6682 144 | 7 | 12 | 4.0868 145 | 8 | 1 | 3.8673 146 | 8 | 2 | 3.8382 147 | 8 | 3 | 3.9121 148 | 8 | 4 | 3.6681 149 | 8 | 5 | 3.4645 150 | 8 | 6 | 3.7144 151 | 8 | 7 | 1.2442 152 | 8 | 8 | 0.0000 153 | 8 | 9 | 0.0380 154 | 8 | 10 | 5.2355 155 | 8 | 11 | 4.8206 156 | 8 | 12 | 4.1464 157 | 9 | 1 | 3.8833 158 | 9 | 2 | 3.8564 159 | 9 | 3 | 3.9258 160 | 9 | 4 | 3.6846 161 | 9 | 5 | 3.4829 162 | 9 | 6 | 3.7347 163 | 9 | 7 | 1.2549 164 | 9 | 8 | 0.0380 165 | 9 | 9 | 0.0000 166 | 9 | 10 | 5.2370 167 | 9 | 11 | 4.8217 168 | 9 | 12 | 4.1514 169 | 10 | 1 | 4.7472 170 | 10 | 2 | 4.9827 171 | 10 | 3 | 4.8348 172 | 10 | 4 | 5.0549 173 | 10 | 5 | 5.1797 174 | 10 | 6 | 5.1326 175 | 10 | 7 | 5.0754 176 | 10 | 8 | 5.2355 177 | 10 | 9 | 5.2370 178 | 10 | 10 | 0.0000 179 | 10 | 11 | 3.0530 180 | 10 | 12 | 3.1208 181 | 11 | 1 | 4.3779 182 | 11 | 2 | 4.5090 183 | 11 | 3 | 4.4050 184 | 11 | 4 | 4.6132 185 | 11 | 5 | 4.7246 186 | 11 | 6 | 4.5488 187 | 11 | 7 | 4.6682 188 | 11 | 8 | 4.8206 189 | 11 | 9 | 4.8217 190 | 11 | 10 | 3.0530 191 | 11 | 11 | 0.0000 192 | 11 | 12 | 3.1781 193 | 12 | 1 | 3.5497 194 | 12 | 2 | 3.7336 195 | 12 | 3 | 3.6224 196 | 12 | 4 | 3.8741 197 | 12 | 5 | 3.8624 198 | 12 | 6 | 3.9115 199 | 12 | 7 | 4.0868 200 | 12 | 8 | 4.1464 201 | 12 | 9 | 4.1514 202 | 12 | 10 | 3.1208 203 | 12 | 11 | 3.1781 204 | 12 | 12 | 0.0000 205 | (144 rows) 206 | 207 | SELECT p1.id, p2.id, round((p1.signature <-> p2.signature)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id; 208 | id | id | round 209 | ----+----+-------- 210 | 1 | 1 | 0.0000 211 | 1 | 2 | 0.5867 212 | 1 | 3 | 0.6410 213 | 1 | 4 | 1.1409 214 | 1 | 5 | 1.3776 215 | 1 | 6 | 1.5149 216 | 1 | 7 | 3.1433 217 | 1 | 8 | 3.4325 218 | 1 | 9 | 3.4649 219 | 1 | 10 | 4.0842 220 | 1 | 11 | 3.3723 221 | 1 | 12 | 2.4935 222 | 2 | 1 | 0.5867 223 | 2 | 2 | 0.0000 224 | 2 | 3 | 0.7878 225 | 2 | 4 | 1.1464 226 | 2 | 5 | 1.2832 227 | 2 | 6 | 1.4290 228 | 2 | 7 | 3.2926 229 | 2 | 8 | 3.5544 230 | 2 | 9 | 3.5892 231 | 2 | 10 | 4.4382 232 | 2 | 11 | 3.6304 233 | 2 | 12 | 2.8089 234 | 3 | 1 | 0.6410 235 | 3 | 2 | 0.7878 236 | 3 | 3 | 0.0000 237 | 3 | 4 | 1.0521 238 | 3 | 5 | 1.3870 239 | 3 | 6 | 1.4914 240 | 3 | 7 | 3.0093 241 | 3 | 8 | 3.2599 242 | 3 | 9 | 3.2895 243 | 3 | 10 | 4.0590 244 | 3 | 11 | 3.2181 245 | 3 | 12 | 2.4356 246 | 4 | 1 | 1.1409 247 | 4 | 2 | 1.1464 248 | 4 | 3 | 1.0521 249 | 4 | 4 | 0.0000 250 | 4 | 5 | 0.5918 251 | 4 | 6 | 0.7625 252 | 4 | 7 | 2.6852 253 | 4 | 8 | 2.9110 254 | 4 | 9 | 2.9435 255 | 4 | 10 | 3.8710 256 | 4 | 11 | 2.9798 257 | 4 | 12 | 2.1815 258 | 5 | 1 | 1.3776 259 | 5 | 2 | 1.2832 260 | 5 | 3 | 1.3870 261 | 5 | 4 | 0.5918 262 | 5 | 5 | 0.0000 263 | 5 | 6 | 0.6988 264 | 5 | 7 | 2.9093 265 | 5 | 8 | 3.1235 266 | 5 | 9 | 3.1573 267 | 5 | 10 | 4.0526 268 | 5 | 11 | 3.2134 269 | 5 | 12 | 2.3678 270 | 6 | 1 | 1.5149 271 | 6 | 2 | 1.4290 272 | 6 | 3 | 1.4914 273 | 6 | 4 | 0.7625 274 | 6 | 5 | 0.6988 275 | 6 | 6 | 0.0000 276 | 6 | 7 | 2.8572 277 | 6 | 8 | 3.0858 278 | 6 | 9 | 3.1190 279 | 6 | 10 | 3.7984 280 | 6 | 11 | 2.7854 281 | 6 | 12 | 2.0468 282 | 7 | 1 | 3.1433 283 | 7 | 2 | 3.2926 284 | 7 | 3 | 3.0093 285 | 7 | 4 | 2.6852 286 | 7 | 5 | 2.9093 287 | 7 | 6 | 2.8572 288 | 7 | 7 | 0.0000 289 | 7 | 8 | 0.6971 290 | 7 | 9 | 0.7130 291 | 7 | 10 | 4.3993 292 | 7 | 11 | 3.5102 293 | 7 | 12 | 3.2894 294 | 8 | 1 | 3.4325 295 | 8 | 2 | 3.5544 296 | 8 | 3 | 3.2599 297 | 8 | 4 | 2.9110 298 | 8 | 5 | 3.1235 299 | 8 | 6 | 3.0858 300 | 8 | 7 | 0.6971 301 | 8 | 8 | 0.0000 302 | 8 | 9 | 0.0421 303 | 8 | 10 | 4.6892 304 | 8 | 11 | 3.7489 305 | 8 | 12 | 3.5759 306 | 9 | 1 | 3.4649 307 | 9 | 2 | 3.5892 308 | 9 | 3 | 3.2895 309 | 9 | 4 | 2.9435 310 | 9 | 5 | 3.1573 311 | 9 | 6 | 3.1190 312 | 9 | 7 | 0.7130 313 | 9 | 8 | 0.0421 314 | 9 | 9 | 0.0000 315 | 9 | 10 | 4.6962 316 | 9 | 11 | 3.7582 317 | 9 | 12 | 3.5938 318 | 10 | 1 | 4.0842 319 | 10 | 2 | 4.4382 320 | 10 | 3 | 4.0590 321 | 10 | 4 | 3.8710 322 | 10 | 5 | 4.0526 323 | 10 | 6 | 3.7984 324 | 10 | 7 | 4.3993 325 | 10 | 8 | 4.6892 326 | 10 | 9 | 4.6962 327 | 10 | 10 | 0.0000 328 | 10 | 11 | 1.8257 329 | 10 | 12 | 2.0829 330 | 11 | 1 | 3.3723 331 | 11 | 2 | 3.6304 332 | 11 | 3 | 3.2181 333 | 11 | 4 | 2.9798 334 | 11 | 5 | 3.2134 335 | 11 | 6 | 2.7854 336 | 11 | 7 | 3.5102 337 | 11 | 8 | 3.7489 338 | 11 | 9 | 3.7582 339 | 11 | 10 | 1.8257 340 | 11 | 11 | 0.0000 341 | 11 | 12 | 1.2968 342 | 12 | 1 | 2.4935 343 | 12 | 2 | 2.8089 344 | 12 | 3 | 2.4356 345 | 12 | 4 | 2.1815 346 | 12 | 5 | 2.3678 347 | 12 | 6 | 2.0468 348 | 12 | 7 | 3.2894 349 | 12 | 8 | 3.5759 350 | 12 | 9 | 3.5938 351 | 12 | 10 | 2.0829 352 | 12 | 11 | 1.2968 353 | 12 | 12 | 0.0000 354 | (144 rows) 355 | 356 | SET enable_seqscan = OFF; 357 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 1) LIMIT 3; 358 | id 359 | ---- 360 | 1 361 | 2 362 | 3 363 | (3 rows) 364 | 365 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 4) LIMIT 3; 366 | id 367 | ---- 368 | 4 369 | 5 370 | 6 371 | (3 rows) 372 | 373 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 7) LIMIT 3; 374 | id 375 | ---- 376 | 7 377 | 8 378 | 9 379 | (3 rows) 380 | 381 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 10) LIMIT 3; 382 | id 383 | ---- 384 | 10 385 | 11 386 | 12 387 | (3 rows) 388 | 389 | -------------------------------------------------------------------------------- /expected/imgsmlr_1.out: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION imgsmlr; 2 | CREATE TABLE image (id integer PRIMARY KEY, data bytea); 3 | CREATE TABLE tmp (data text); 4 | \copy tmp from 'data/1.jpg.hex' 5 | INSERT INTO image VALUES (1, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 6 | TRUNCATE tmp; 7 | \copy tmp from 'data/2.png.hex' 8 | INSERT INTO image VALUES (2, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 9 | TRUNCATE tmp; 10 | \copy tmp from 'data/3.gif.hex' 11 | INSERT INTO image VALUES (3, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 12 | TRUNCATE tmp; 13 | \copy tmp from 'data/4.jpg.hex' 14 | INSERT INTO image VALUES (4, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 15 | TRUNCATE tmp; 16 | \copy tmp from 'data/5.png.hex' 17 | INSERT INTO image VALUES (5, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 18 | TRUNCATE tmp; 19 | \copy tmp from 'data/6.gif.hex' 20 | INSERT INTO image VALUES (6, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 21 | TRUNCATE tmp; 22 | \copy tmp from 'data/7.jpg.hex' 23 | INSERT INTO image VALUES (7, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 24 | TRUNCATE tmp; 25 | \copy tmp from 'data/8.png.hex' 26 | INSERT INTO image VALUES (8, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 27 | TRUNCATE tmp; 28 | \copy tmp from 'data/9.gif.hex' 29 | INSERT INTO image VALUES (9, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 30 | TRUNCATE tmp; 31 | \copy tmp from 'data/10.jpg.hex' 32 | INSERT INTO image VALUES (10, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 33 | TRUNCATE tmp; 34 | \copy tmp from 'data/11.png.hex' 35 | INSERT INTO image VALUES (11, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 36 | TRUNCATE tmp; 37 | \copy tmp from 'data/12.gif.hex' 38 | INSERT INTO image VALUES (12, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 39 | TRUNCATE tmp; 40 | CREATE TABLE pat AS ( 41 | SELECT 42 | id, 43 | shuffle_pattern(pattern)::text::pattern AS pattern, 44 | pattern2signature(pattern)::text::signature AS signature 45 | FROM ( 46 | SELECT 47 | id, 48 | (CASE WHEN id % 3 = 1 THEN jpeg2pattern(data) 49 | WHEN id % 3 = 2 THEN png2pattern(data) 50 | WHEN id % 3 = 0 THEN gif2pattern(data) 51 | ELSE NULL END) AS pattern 52 | FROM 53 | image 54 | ) x 55 | ); 56 | ALTER TABLE pat ADD PRIMARY KEY (id); 57 | CREATE INDEX pat_signature_idx ON pat USING gist (signature); 58 | SELECT p1.id, p2.id, round((p1.pattern <-> p2.pattern)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id; 59 | id | id | round 60 | ----+----+-------- 61 | 1 | 1 | 0.0000 62 | 1 | 2 | 1.5479 63 | 1 | 3 | 1.6157 64 | 1 | 4 | 2.8541 65 | 1 | 5 | 3.1804 66 | 1 | 6 | 2.7211 67 | 1 | 7 | 3.7726 68 | 1 | 8 | 3.8715 69 | 1 | 9 | 3.8874 70 | 1 | 10 | 4.7461 71 | 1 | 11 | 4.3753 72 | 1 | 12 | 3.5495 73 | 2 | 1 | 1.5479 74 | 2 | 2 | 0.0000 75 | 2 | 3 | 1.2575 76 | 2 | 4 | 2.7383 77 | 2 | 5 | 2.8913 78 | 2 | 6 | 2.6645 79 | 2 | 7 | 3.7857 80 | 2 | 8 | 3.8382 81 | 2 | 9 | 3.8564 82 | 2 | 10 | 4.9827 83 | 2 | 11 | 4.5090 84 | 2 | 12 | 3.7336 85 | 3 | 1 | 1.6157 86 | 3 | 2 | 1.2575 87 | 3 | 3 | 0.0000 88 | 3 | 4 | 2.7807 89 | 3 | 5 | 3.0869 90 | 3 | 6 | 2.7949 91 | 3 | 7 | 3.8350 92 | 3 | 8 | 3.9121 93 | 3 | 9 | 3.9258 94 | 3 | 10 | 4.8348 95 | 3 | 11 | 4.4050 96 | 3 | 12 | 3.6224 97 | 4 | 1 | 2.8541 98 | 4 | 2 | 2.7383 99 | 4 | 3 | 2.7807 100 | 4 | 4 | 0.0000 101 | 4 | 5 | 2.4250 102 | 4 | 6 | 2.0934 103 | 4 | 7 | 3.7260 104 | 4 | 8 | 3.6682 105 | 4 | 9 | 3.6847 106 | 4 | 10 | 5.0547 107 | 4 | 11 | 4.6130 108 | 4 | 12 | 3.8739 109 | 5 | 1 | 3.1804 110 | 5 | 2 | 2.8913 111 | 5 | 3 | 3.0869 112 | 5 | 4 | 2.4250 113 | 5 | 5 | 0.0000 114 | 5 | 6 | 2.9383 115 | 5 | 7 | 3.5109 116 | 5 | 8 | 3.4645 117 | 5 | 9 | 3.4829 118 | 5 | 10 | 5.1797 119 | 5 | 11 | 4.7246 120 | 5 | 12 | 3.8624 121 | 6 | 1 | 2.7211 122 | 6 | 2 | 2.6645 123 | 6 | 3 | 2.7949 124 | 6 | 4 | 2.0934 125 | 6 | 5 | 2.9383 126 | 6 | 6 | 0.0000 127 | 6 | 7 | 3.6541 128 | 6 | 8 | 3.7144 129 | 6 | 9 | 3.7347 130 | 6 | 10 | 5.1326 131 | 6 | 11 | 4.5488 132 | 6 | 12 | 3.9115 133 | 7 | 1 | 3.7726 134 | 7 | 2 | 3.7857 135 | 7 | 3 | 3.8350 136 | 7 | 4 | 3.7260 137 | 7 | 5 | 3.5109 138 | 7 | 6 | 3.6541 139 | 7 | 7 | 0.0000 140 | 7 | 8 | 1.2449 141 | 7 | 9 | 1.2556 142 | 7 | 10 | 5.0754 143 | 7 | 11 | 4.6677 144 | 7 | 12 | 4.0871 145 | 8 | 1 | 3.8715 146 | 8 | 2 | 3.8382 147 | 8 | 3 | 3.9121 148 | 8 | 4 | 3.6682 149 | 8 | 5 | 3.4645 150 | 8 | 6 | 3.7144 151 | 8 | 7 | 1.2449 152 | 8 | 8 | 0.0000 153 | 8 | 9 | 0.0380 154 | 8 | 10 | 5.2355 155 | 8 | 11 | 4.8206 156 | 8 | 12 | 4.1464 157 | 9 | 1 | 3.8874 158 | 9 | 2 | 3.8564 159 | 9 | 3 | 3.9258 160 | 9 | 4 | 3.6847 161 | 9 | 5 | 3.4829 162 | 9 | 6 | 3.7347 163 | 9 | 7 | 1.2556 164 | 9 | 8 | 0.0380 165 | 9 | 9 | 0.0000 166 | 9 | 10 | 5.2370 167 | 9 | 11 | 4.8217 168 | 9 | 12 | 4.1514 169 | 10 | 1 | 4.7461 170 | 10 | 2 | 4.9827 171 | 10 | 3 | 4.8348 172 | 10 | 4 | 5.0547 173 | 10 | 5 | 5.1797 174 | 10 | 6 | 5.1326 175 | 10 | 7 | 5.0754 176 | 10 | 8 | 5.2355 177 | 10 | 9 | 5.2370 178 | 10 | 10 | 0.0000 179 | 10 | 11 | 3.0530 180 | 10 | 12 | 3.1208 181 | 11 | 1 | 4.3753 182 | 11 | 2 | 4.5090 183 | 11 | 3 | 4.4050 184 | 11 | 4 | 4.6130 185 | 11 | 5 | 4.7246 186 | 11 | 6 | 4.5488 187 | 11 | 7 | 4.6677 188 | 11 | 8 | 4.8206 189 | 11 | 9 | 4.8217 190 | 11 | 10 | 3.0530 191 | 11 | 11 | 0.0000 192 | 11 | 12 | 3.1781 193 | 12 | 1 | 3.5495 194 | 12 | 2 | 3.7336 195 | 12 | 3 | 3.6224 196 | 12 | 4 | 3.8739 197 | 12 | 5 | 3.8624 198 | 12 | 6 | 3.9115 199 | 12 | 7 | 4.0871 200 | 12 | 8 | 4.1464 201 | 12 | 9 | 4.1514 202 | 12 | 10 | 3.1208 203 | 12 | 11 | 3.1781 204 | 12 | 12 | 0.0000 205 | (144 rows) 206 | 207 | SELECT p1.id, p2.id, round((p1.signature <-> p2.signature)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id; 208 | id | id | round 209 | ----+----+-------- 210 | 1 | 1 | 0.0000 211 | 1 | 2 | 0.5960 212 | 1 | 3 | 0.6352 213 | 1 | 4 | 1.1418 214 | 1 | 5 | 1.3814 215 | 1 | 6 | 1.5187 216 | 1 | 7 | 3.1406 217 | 1 | 8 | 3.4291 218 | 1 | 9 | 3.4613 219 | 1 | 10 | 4.0780 220 | 1 | 11 | 3.3670 221 | 1 | 12 | 2.4889 222 | 2 | 1 | 0.5960 223 | 2 | 2 | 0.0000 224 | 2 | 3 | 0.7878 225 | 2 | 4 | 1.1465 226 | 2 | 5 | 1.2832 227 | 2 | 6 | 1.4290 228 | 2 | 7 | 3.2934 229 | 2 | 8 | 3.5544 230 | 2 | 9 | 3.5892 231 | 2 | 10 | 4.4382 232 | 2 | 11 | 3.6304 233 | 2 | 12 | 2.8089 234 | 3 | 1 | 0.6352 235 | 3 | 2 | 0.7878 236 | 3 | 3 | 0.0000 237 | 3 | 4 | 1.0521 238 | 3 | 5 | 1.3870 239 | 3 | 6 | 1.4914 240 | 3 | 7 | 3.0100 241 | 3 | 8 | 3.2599 242 | 3 | 9 | 3.2895 243 | 3 | 10 | 4.0590 244 | 3 | 11 | 3.2181 245 | 3 | 12 | 2.4356 246 | 4 | 1 | 1.1418 247 | 4 | 2 | 1.1465 248 | 4 | 3 | 1.0521 249 | 4 | 4 | 0.0000 250 | 4 | 5 | 0.5920 251 | 4 | 6 | 0.7626 252 | 4 | 7 | 2.6859 253 | 4 | 8 | 2.9110 254 | 4 | 9 | 2.9435 255 | 4 | 10 | 3.8710 256 | 4 | 11 | 2.9797 257 | 4 | 12 | 2.1815 258 | 5 | 1 | 1.3814 259 | 5 | 2 | 1.2832 260 | 5 | 3 | 1.3870 261 | 5 | 4 | 0.5920 262 | 5 | 5 | 0.0000 263 | 5 | 6 | 0.6988 264 | 5 | 7 | 2.9100 265 | 5 | 8 | 3.1235 266 | 5 | 9 | 3.1573 267 | 5 | 10 | 4.0526 268 | 5 | 11 | 3.2134 269 | 5 | 12 | 2.3678 270 | 6 | 1 | 1.5187 271 | 6 | 2 | 1.4290 272 | 6 | 3 | 1.4914 273 | 6 | 4 | 0.7626 274 | 6 | 5 | 0.6988 275 | 6 | 6 | 0.0000 276 | 6 | 7 | 2.8577 277 | 6 | 8 | 3.0858 278 | 6 | 9 | 3.1190 279 | 6 | 10 | 3.7984 280 | 6 | 11 | 2.7854 281 | 6 | 12 | 2.0468 282 | 7 | 1 | 3.1406 283 | 7 | 2 | 3.2934 284 | 7 | 3 | 3.0100 285 | 7 | 4 | 2.6859 286 | 7 | 5 | 2.9100 287 | 7 | 6 | 2.8577 288 | 7 | 7 | 0.0000 289 | 7 | 8 | 0.6968 290 | 7 | 9 | 0.7127 291 | 7 | 10 | 4.3989 292 | 7 | 11 | 3.5097 293 | 7 | 12 | 3.2894 294 | 8 | 1 | 3.4291 295 | 8 | 2 | 3.5544 296 | 8 | 3 | 3.2599 297 | 8 | 4 | 2.9110 298 | 8 | 5 | 3.1235 299 | 8 | 6 | 3.0858 300 | 8 | 7 | 0.6968 301 | 8 | 8 | 0.0000 302 | 8 | 9 | 0.0421 303 | 8 | 10 | 4.6892 304 | 8 | 11 | 3.7489 305 | 8 | 12 | 3.5759 306 | 9 | 1 | 3.4613 307 | 9 | 2 | 3.5892 308 | 9 | 3 | 3.2895 309 | 9 | 4 | 2.9435 310 | 9 | 5 | 3.1573 311 | 9 | 6 | 3.1190 312 | 9 | 7 | 0.7127 313 | 9 | 8 | 0.0421 314 | 9 | 9 | 0.0000 315 | 9 | 10 | 4.6962 316 | 9 | 11 | 3.7582 317 | 9 | 12 | 3.5938 318 | 10 | 1 | 4.0780 319 | 10 | 2 | 4.4382 320 | 10 | 3 | 4.0590 321 | 10 | 4 | 3.8710 322 | 10 | 5 | 4.0526 323 | 10 | 6 | 3.7984 324 | 10 | 7 | 4.3989 325 | 10 | 8 | 4.6892 326 | 10 | 9 | 4.6962 327 | 10 | 10 | 0.0000 328 | 10 | 11 | 1.8257 329 | 10 | 12 | 2.0829 330 | 11 | 1 | 3.3670 331 | 11 | 2 | 3.6304 332 | 11 | 3 | 3.2181 333 | 11 | 4 | 2.9797 334 | 11 | 5 | 3.2134 335 | 11 | 6 | 2.7854 336 | 11 | 7 | 3.5097 337 | 11 | 8 | 3.7489 338 | 11 | 9 | 3.7582 339 | 11 | 10 | 1.8257 340 | 11 | 11 | 0.0000 341 | 11 | 12 | 1.2968 342 | 12 | 1 | 2.4889 343 | 12 | 2 | 2.8089 344 | 12 | 3 | 2.4356 345 | 12 | 4 | 2.1815 346 | 12 | 5 | 2.3678 347 | 12 | 6 | 2.0468 348 | 12 | 7 | 3.2894 349 | 12 | 8 | 3.5759 350 | 12 | 9 | 3.5938 351 | 12 | 10 | 2.0829 352 | 12 | 11 | 1.2968 353 | 12 | 12 | 0.0000 354 | (144 rows) 355 | 356 | SET enable_seqscan = OFF; 357 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 1) LIMIT 3; 358 | id 359 | ---- 360 | 1 361 | 2 362 | 3 363 | (3 rows) 364 | 365 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 4) LIMIT 3; 366 | id 367 | ---- 368 | 4 369 | 5 370 | 6 371 | (3 rows) 372 | 373 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 7) LIMIT 3; 374 | id 375 | ---- 376 | 7 377 | 8 378 | 9 379 | (3 rows) 380 | 381 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 10) LIMIT 3; 382 | id 383 | ---- 384 | 10 385 | 11 386 | 12 387 | (3 rows) 388 | 389 | -------------------------------------------------------------------------------- /imgsmlr--1.0.sql: -------------------------------------------------------------------------------- 1 | /* imgsmlr/imgsmlr--1.0.sql */ 2 | 3 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 4 | \echo Use "CREATE EXTENSION imgsmlr" to load this file. \quit 5 | 6 | -- 7 | -- PostgreSQL code for IMGSMLR. 8 | -- 9 | 10 | CREATE FUNCTION pattern_in(cstring) 11 | RETURNS pattern 12 | AS 'MODULE_PATHNAME' 13 | LANGUAGE C IMMUTABLE STRICT; 14 | 15 | CREATE FUNCTION pattern_out(pattern) 16 | RETURNS cstring 17 | AS 'MODULE_PATHNAME' 18 | LANGUAGE C IMMUTABLE STRICT; 19 | 20 | CREATE TYPE pattern ( 21 | INTERNALLENGTH = -1, 22 | INPUT = pattern_in, 23 | OUTPUT = pattern_out, 24 | STORAGE = extended 25 | ); 26 | 27 | CREATE FUNCTION signature_in(cstring) 28 | RETURNS signature 29 | AS 'MODULE_PATHNAME' 30 | LANGUAGE C IMMUTABLE STRICT; 31 | 32 | CREATE FUNCTION signature_out(signature) 33 | RETURNS cstring 34 | AS 'MODULE_PATHNAME' 35 | LANGUAGE C IMMUTABLE STRICT; 36 | 37 | CREATE TYPE signature ( 38 | INTERNALLENGTH = 64, 39 | INPUT = signature_in, 40 | OUTPUT = signature_out, 41 | ALIGNMENT = float 42 | ); 43 | 44 | CREATE FUNCTION jpeg2pattern(bytea) 45 | RETURNS pattern 46 | AS 'MODULE_PATHNAME' 47 | LANGUAGE C IMMUTABLE STRICT; 48 | 49 | CREATE FUNCTION png2pattern(bytea) 50 | RETURNS pattern 51 | AS 'MODULE_PATHNAME' 52 | LANGUAGE C IMMUTABLE STRICT; 53 | 54 | CREATE FUNCTION gif2pattern(bytea) 55 | RETURNS pattern 56 | AS 'MODULE_PATHNAME' 57 | LANGUAGE C IMMUTABLE STRICT; 58 | 59 | CREATE FUNCTION pattern2signature(pattern) 60 | RETURNS signature 61 | AS 'MODULE_PATHNAME' 62 | LANGUAGE C IMMUTABLE STRICT; 63 | 64 | CREATE FUNCTION pattern_distance(pattern, pattern) 65 | RETURNS float4 66 | AS 'MODULE_PATHNAME' 67 | LANGUAGE C IMMUTABLE STRICT; 68 | 69 | CREATE FUNCTION signature_distance(signature, signature) 70 | RETURNS float4 71 | AS 'MODULE_PATHNAME' 72 | LANGUAGE C IMMUTABLE STRICT; 73 | 74 | CREATE OPERATOR <-> ( 75 | LEFTARG = pattern, 76 | RIGHTARG = pattern, 77 | PROCEDURE = pattern_distance 78 | ); 79 | 80 | CREATE OPERATOR <-> ( 81 | LEFTARG = signature, 82 | RIGHTARG = signature, 83 | PROCEDURE = signature_distance 84 | ); 85 | 86 | CREATE FUNCTION shuffle_pattern(pattern) 87 | RETURNS pattern 88 | AS 'MODULE_PATHNAME' 89 | LANGUAGE C IMMUTABLE STRICT; 90 | 91 | CREATE FUNCTION signature_consistent(internal,signature,int,oid,internal) 92 | RETURNS bool 93 | AS 'MODULE_PATHNAME' 94 | LANGUAGE C IMMUTABLE STRICT; 95 | 96 | CREATE FUNCTION signature_compress(internal) 97 | RETURNS internal 98 | AS 'MODULE_PATHNAME' 99 | LANGUAGE C IMMUTABLE STRICT; 100 | 101 | CREATE FUNCTION signature_decompress(internal) 102 | RETURNS internal 103 | AS 'MODULE_PATHNAME' 104 | LANGUAGE C IMMUTABLE STRICT; 105 | 106 | CREATE FUNCTION signature_penalty(internal,internal,internal) 107 | RETURNS internal 108 | AS 'MODULE_PATHNAME' 109 | LANGUAGE C IMMUTABLE STRICT; 110 | 111 | CREATE FUNCTION signature_picksplit(internal, internal) 112 | RETURNS internal 113 | AS 'MODULE_PATHNAME' 114 | LANGUAGE C IMMUTABLE STRICT; 115 | 116 | CREATE FUNCTION signature_union(internal, internal) 117 | RETURNS bytea 118 | AS 'MODULE_PATHNAME' 119 | LANGUAGE C IMMUTABLE STRICT; 120 | 121 | CREATE FUNCTION signature_same(bytea, bytea, internal) 122 | RETURNS internal 123 | AS 'MODULE_PATHNAME' 124 | LANGUAGE C IMMUTABLE STRICT; 125 | 126 | CREATE FUNCTION signature_gist_distance(internal, text, int, oid) 127 | RETURNS float8 128 | AS 'MODULE_PATHNAME' 129 | LANGUAGE C IMMUTABLE STRICT; 130 | 131 | CREATE OPERATOR CLASS gist_signature_ops 132 | DEFAULT FOR TYPE signature USING gist AS 133 | OPERATOR 1 <-> FOR ORDER BY pg_catalog.float_ops, 134 | FUNCTION 1 signature_consistent (internal, signature, int, oid, internal), 135 | FUNCTION 2 signature_union (internal, internal), 136 | FUNCTION 3 signature_compress (internal), 137 | FUNCTION 4 signature_decompress (internal), 138 | FUNCTION 5 signature_penalty (internal, internal, internal), 139 | FUNCTION 6 signature_picksplit (internal, internal), 140 | FUNCTION 7 signature_same (bytea, bytea, internal), 141 | FUNCTION 8 signature_gist_distance (internal, text, int, oid), 142 | STORAGE bytea; 143 | -------------------------------------------------------------------------------- /imgsmlr.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * Image similarity extension 4 | * 5 | * Copyright (c) 2015, PostgreSQL Global Development Group 6 | * 7 | * This software is released under the PostgreSQL Licence. 8 | * 9 | * Author: Alexander Korotkov 10 | * 11 | * IDENTIFICATION 12 | * imgsmlr/imgsmlr.c 13 | *------------------------------------------------------------------------- 14 | */ 15 | #include "postgres.h" 16 | 17 | #include "c.h" 18 | #include "fmgr.h" 19 | #include "imgsmlr.h" 20 | #include "lib/stringinfo.h" 21 | #include "utils/builtins.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define DEBUG_PRINT 28 | 29 | PG_MODULE_MAGIC; 30 | 31 | PG_FUNCTION_INFO_V1(jpeg2pattern); 32 | Datum jpeg2pattern(PG_FUNCTION_ARGS); 33 | PG_FUNCTION_INFO_V1(png2pattern); 34 | Datum png2pattern(PG_FUNCTION_ARGS); 35 | PG_FUNCTION_INFO_V1(gif2pattern); 36 | Datum gif2pattern(PG_FUNCTION_ARGS); 37 | PG_FUNCTION_INFO_V1(pattern2signature); 38 | Datum pattern2signature(PG_FUNCTION_ARGS); 39 | PG_FUNCTION_INFO_V1(pattern_in); 40 | Datum pattern_in(PG_FUNCTION_ARGS); 41 | PG_FUNCTION_INFO_V1(pattern_out); 42 | Datum pattern_out(PG_FUNCTION_ARGS); 43 | PG_FUNCTION_INFO_V1(signature_in); 44 | Datum signature_in(PG_FUNCTION_ARGS); 45 | PG_FUNCTION_INFO_V1(signature_out); 46 | Datum signature_out(PG_FUNCTION_ARGS); 47 | PG_FUNCTION_INFO_V1(pattern_distance); 48 | Datum pattern_distance(PG_FUNCTION_ARGS); 49 | PG_FUNCTION_INFO_V1(signature_distance); 50 | Datum signature_distance(PG_FUNCTION_ARGS); 51 | PG_FUNCTION_INFO_V1(shuffle_pattern); 52 | Datum shuffle_pattern(PG_FUNCTION_ARGS); 53 | 54 | static Pattern *image2pattern(gdImagePtr im); 55 | static void makePattern(gdImagePtr im, PatternData *pattern); 56 | static void normalizePattern(PatternData *pattern); 57 | static void waveletTransform(PatternData *dst, PatternData *src, int size); 58 | static float calcSumm(PatternData *pattern, int x, int y, int sX, int sY); 59 | static void calcSignature(PatternData *pattern, Signature *signature); 60 | static float calcDiff(PatternData *patternA, PatternData *patternB, int x, int y, int sX, int sY); 61 | static void shuffle(PatternData *dst, PatternData *src, int x, int y, int sX, int sY, int w); 62 | static float read_float(char **s, char *type_name, char *orig_string); 63 | 64 | #ifdef DEBUG_INFO 65 | static void debugPrintPattern(PatternData *pattern, const char *filename, bool color); 66 | static void debugPrintSignature(Signature *signature, const char *filename); 67 | #endif 68 | 69 | /* 70 | * Transform GD image into pattern. 71 | */ 72 | static Pattern * 73 | image2pattern(gdImagePtr im) 74 | { 75 | gdImagePtr tb; 76 | Pattern *pattern; 77 | PatternData source; 78 | 79 | /* Resize image */ 80 | tb = gdImageCreateTrueColor(PATTERN_SIZE, PATTERN_SIZE); 81 | if (!tb) 82 | { 83 | elog(NOTICE, "Error creating pattern"); 84 | return NULL; 85 | } 86 | gdImageCopyResampled(tb, im, 0, 0, 0, 0, PATTERN_SIZE, PATTERN_SIZE, 87 | im->sx, im->sy); 88 | 89 | /* Create source pattern as greyscale image */ 90 | makePattern(tb, &source); 91 | gdImageDestroy(tb); 92 | 93 | #ifdef DEBUG_INFO 94 | debugPrintPattern(&source, "/tmp/pattern1.raw", false); 95 | #endif 96 | 97 | /* "Normalize" intensiveness in the pattern */ 98 | normalizePattern(&source); 99 | 100 | #ifdef DEBUG_INFO 101 | debugPrintPattern(&source, "/tmp/pattern2.raw", false); 102 | #endif 103 | 104 | /* Allocate pattern */ 105 | pattern = (Pattern *)palloc(sizeof(Pattern)); 106 | SET_VARSIZE(pattern, sizeof(Pattern)); 107 | 108 | /* Do wavelet transform */ 109 | waveletTransform(&pattern->data, &source, PATTERN_SIZE); 110 | 111 | #ifdef DEBUG_INFO 112 | debugPrintPattern(transformed, "/tmp/pattern3.raw", true); 113 | #endif 114 | 115 | return pattern; 116 | } 117 | 118 | /* 119 | * Load pattern from jpeg image in bytea. 120 | */ 121 | Datum 122 | jpeg2pattern(PG_FUNCTION_ARGS) 123 | { 124 | bytea *img = PG_GETARG_BYTEA_P(0); 125 | Pattern *pattern; 126 | gdImagePtr im; 127 | 128 | im = gdImageCreateFromJpegPtr(VARSIZE_ANY_EXHDR(img), VARDATA_ANY(img)); 129 | PG_FREE_IF_COPY(img, 0); 130 | if (!im) 131 | { 132 | elog(NOTICE, "Error loading jpeg"); 133 | PG_RETURN_NULL(); 134 | } 135 | pattern = image2pattern(im); 136 | gdImageDestroy(im); 137 | 138 | if (pattern) 139 | PG_RETURN_BYTEA_P(pattern); 140 | else 141 | PG_RETURN_NULL(); 142 | } 143 | 144 | /* 145 | * Load pattern from png image in bytea. 146 | */ 147 | Datum 148 | png2pattern(PG_FUNCTION_ARGS) 149 | { 150 | bytea *img = PG_GETARG_BYTEA_P(0); 151 | Pattern *pattern; 152 | gdImagePtr im; 153 | 154 | im = gdImageCreateFromPngPtr(VARSIZE_ANY_EXHDR(img), VARDATA_ANY(img)); 155 | PG_FREE_IF_COPY(img, 0); 156 | if (!im) 157 | { 158 | elog(NOTICE, "Error loading png"); 159 | PG_RETURN_NULL(); 160 | } 161 | pattern = image2pattern(im); 162 | gdImageDestroy(im); 163 | 164 | if (pattern) 165 | PG_RETURN_BYTEA_P(pattern); 166 | else 167 | PG_RETURN_NULL(); 168 | } 169 | 170 | /* 171 | * Load pattern from png image in bytea. 172 | */ 173 | Datum 174 | gif2pattern(PG_FUNCTION_ARGS) 175 | { 176 | bytea *img = PG_GETARG_BYTEA_P(0); 177 | Pattern *pattern; 178 | gdImagePtr im; 179 | 180 | im = gdImageCreateFromGifPtr(VARSIZE_ANY_EXHDR(img), VARDATA_ANY(img)); 181 | PG_FREE_IF_COPY(img, 0); 182 | if (!im) 183 | { 184 | elog(NOTICE, "Error loading gif"); 185 | PG_RETURN_NULL(); 186 | } 187 | pattern = image2pattern(im); 188 | gdImageDestroy(im); 189 | 190 | if (pattern) 191 | PG_RETURN_BYTEA_P(pattern); 192 | else 193 | PG_RETURN_NULL(); 194 | } 195 | 196 | /* 197 | * Extract signature from pattern. 198 | */ 199 | Datum 200 | pattern2signature(PG_FUNCTION_ARGS) 201 | { 202 | bytea *patternData = PG_GETARG_BYTEA_P(0); 203 | PatternData *pattern = (PatternData *)VARDATA_ANY(patternData); 204 | Signature *signature = (Signature *)palloc(sizeof(Signature)); 205 | 206 | calcSignature(pattern, signature); 207 | PG_FREE_IF_COPY(patternData, 0); 208 | 209 | #ifdef DEBUG_INFO 210 | debugPrintSignature(signature, "/tmp/signature.raw"); 211 | #endif 212 | 213 | PG_RETURN_POINTER(signature); 214 | } 215 | 216 | /* 217 | * Shuffle pattern values in order to make further comparisons less sensitive 218 | * to shift. Shuffling is actually a build of "w" radius in rectangle 219 | * "(x, y) - (x + sX, y + sY)". 220 | */ 221 | static void 222 | shuffle(PatternData *dst, PatternData *src, int x, int y, int sX, int sY, int w) 223 | { 224 | int i, j; 225 | 226 | for (i = x; i < x + sX; i++) 227 | { 228 | for (j = y; j < y + sY; j++) 229 | { 230 | int ii, jj; 231 | int ii_min = Max(x, i - w), 232 | ii_max = Min(x + sX, i + w + 1), 233 | jj_min = Max(y, j - w), 234 | jj_max = Min(y + sY, j + w + 1); 235 | float sum = 0.0f, sum_r = 0.0f; 236 | 237 | for (ii = ii_min; ii < ii_max; ii++) 238 | { 239 | for (jj = jj_min; jj < jj_max; jj++) 240 | { 241 | float r = (i - ii) * (i - ii) + (j - jj) * (j - jj); 242 | r = 1.0f - sqrt(r) / (float)w; 243 | if (r <= 0.0f) 244 | continue; 245 | sum += src->values[ii][jj] * src->values[ii][jj] * r; 246 | sum_r += r; 247 | } 248 | } 249 | Assert (sum >= 0.0f); 250 | Assert (sum_r > 0.0f); 251 | dst->values[i][j] = sqrt(sum / sum_r); 252 | } 253 | } 254 | } 255 | 256 | /* 257 | * Shuffle pattern: call "shuffle" for each region of wavelet-transformed 258 | * pattern. For each region, blur radius is selected accordingly to its size; 259 | */ 260 | Datum 261 | shuffle_pattern(PG_FUNCTION_ARGS) 262 | { 263 | bytea *patternDataSrc = PG_GETARG_BYTEA_P(0); 264 | bytea *patternDataDst; 265 | PatternData *patternDst, *patternSrc; 266 | int size = PATTERN_SIZE; 267 | 268 | patternDataDst = (bytea *)palloc(VARSIZE_ANY(patternDataSrc)); 269 | memcpy(patternDataDst, patternDataSrc, VARSIZE_ANY(patternDataSrc)); 270 | patternSrc = (PatternData *)VARDATA_ANY(patternDataSrc); 271 | patternDst = (PatternData *)VARDATA_ANY(patternDataDst); 272 | 273 | while (size > 4) 274 | { 275 | size /= 2; 276 | shuffle(patternDst, patternSrc, size, 0, size, size, size / 4); 277 | shuffle(patternDst, patternSrc, 0, size, size, size, size / 4); 278 | shuffle(patternDst, patternSrc, size, size, size, size, size / 4); 279 | } 280 | #ifdef DEBUG_INFO 281 | debugPrintPattern(patternDst, "/tmp/pattern4.raw", false); 282 | #endif 283 | 284 | PG_FREE_IF_COPY(patternDataSrc, 0); 285 | 286 | PG_RETURN_POINTER(patternDataDst); 287 | } 288 | 289 | /* 290 | * Read float4 from string while skipping " ()," symbols. 291 | */ 292 | static float 293 | read_float(char **s, char *type_name, char *orig_string) 294 | { 295 | char c, 296 | *start; 297 | float result; 298 | 299 | while (true) 300 | { 301 | c = **s; 302 | switch (c) 303 | { 304 | case ' ': 305 | case '(': 306 | case ')': 307 | case ',': 308 | (*s)++; 309 | continue; 310 | case '\0': 311 | ereport(ERROR, 312 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 313 | errmsg("invalid input syntax for type %s: \"%s\"", 314 | type_name, orig_string))); 315 | default: 316 | break; 317 | } 318 | break; 319 | } 320 | 321 | start = *s; 322 | result = strtof(start, s); 323 | 324 | if (start == *s) 325 | ereport(ERROR, 326 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 327 | errmsg("invalid input syntax for type %s: \"%s\"", 328 | type_name, orig_string))); 329 | 330 | return result; 331 | } 332 | 333 | /* 334 | * Input "pattern" type from its textual representation. 335 | */ 336 | Datum 337 | pattern_in(PG_FUNCTION_ARGS) 338 | { 339 | char *source = PG_GETARG_CSTRING(0); 340 | Pattern *pattern = (Pattern *) palloc(sizeof(Pattern)); 341 | char *s; 342 | int i, j; 343 | 344 | SET_VARSIZE(pattern, sizeof(Pattern)); 345 | s = source; 346 | for (i = 0; i < PATTERN_SIZE; i++) 347 | for (j = 0; j < PATTERN_SIZE; j++) 348 | pattern->data.values[i][j] = read_float(&s, "pattern", source); 349 | 350 | PG_RETURN_POINTER(pattern); 351 | } 352 | 353 | /* 354 | * Output for type "pattern": return textual representation of matrix. 355 | */ 356 | Datum 357 | pattern_out(PG_FUNCTION_ARGS) 358 | { 359 | bytea *patternData = PG_GETARG_BYTEA_P(0); 360 | PatternData *pattern = (PatternData *) VARDATA_ANY(patternData); 361 | StringInfoData buf; 362 | int i, j; 363 | 364 | initStringInfo(&buf); 365 | 366 | appendStringInfoChar(&buf, '('); 367 | for (i = 0; i < PATTERN_SIZE; i++) 368 | { 369 | if (i > 0) 370 | appendStringInfo(&buf, ", "); 371 | appendStringInfoChar(&buf, '('); 372 | for (j = 0; j < PATTERN_SIZE; j++) 373 | { 374 | if (j > 0) 375 | appendStringInfo(&buf, ", "); 376 | appendStringInfo(&buf, "%f", pattern->values[i][j]); 377 | } 378 | appendStringInfoChar(&buf, ')'); 379 | } 380 | appendStringInfoChar(&buf, ')'); 381 | 382 | PG_FREE_IF_COPY(patternData, 0); 383 | PG_RETURN_CSTRING(buf.data); 384 | } 385 | 386 | /* 387 | * Input "signature" type from its textual representation. 388 | */ 389 | Datum 390 | signature_in(PG_FUNCTION_ARGS) 391 | { 392 | char *source = PG_GETARG_CSTRING(0); 393 | Signature *signature = (Signature *) palloc(sizeof(Signature)); 394 | char *s; 395 | int i; 396 | 397 | SET_VARSIZE(signature, sizeof(Signature)); 398 | s = source; 399 | for (i = 0; i < SIGNATURE_SIZE; i++) 400 | signature->values[i] = read_float(&s, "signature", source); 401 | 402 | PG_RETURN_POINTER(signature); 403 | } 404 | 405 | /* 406 | * Output for type "signature": return textual representation of vector. 407 | */ 408 | Datum 409 | signature_out(PG_FUNCTION_ARGS) 410 | { 411 | Signature *signature = (Signature *)PG_GETARG_POINTER(0); 412 | StringInfoData buf; 413 | int i; 414 | 415 | initStringInfo(&buf); 416 | 417 | appendStringInfoChar(&buf, '('); 418 | for (i = 0; i < SIGNATURE_SIZE; i++) 419 | { 420 | if (i > 0) 421 | appendStringInfo(&buf, ", "); 422 | appendStringInfo(&buf, "%f", signature->values[i]); 423 | } 424 | appendStringInfoChar(&buf, ')'); 425 | 426 | PG_FREE_IF_COPY(signature, 0); 427 | PG_RETURN_CSTRING(buf.data); 428 | } 429 | 430 | /* 431 | * Calculate summary of square difference between "patternA" and "patternB" 432 | * in rectangle "(x, y) - (x + sX, y + sY)". 433 | */ 434 | static float 435 | calcDiff(PatternData *patternA, PatternData *patternB, int x, int y, 436 | int sX, int sY) 437 | { 438 | int i, j; 439 | float summ = 0.0f, val; 440 | for (i = x; i < x + sX; i++) 441 | { 442 | for (j = y; j < y + sY; j++) 443 | { 444 | val = patternA->values[i][j] 445 | - patternB->values[i][j]; 446 | summ += val * val; 447 | } 448 | } 449 | return summ; 450 | } 451 | 452 | /* 453 | * Distance between patterns is square root of the summary of difference between 454 | * regions of wavelet-transformed pattern corrected by their sized. Difference 455 | * of each region is summary of square difference between values. 456 | */ 457 | Datum 458 | pattern_distance(PG_FUNCTION_ARGS) 459 | { 460 | bytea *patternDataA = PG_GETARG_BYTEA_P(0); 461 | PatternData *patternA = (PatternData *)VARDATA_ANY(patternDataA); 462 | bytea *patternDataB = PG_GETARG_BYTEA_P(1); 463 | PatternData *patternB = (PatternData *)VARDATA_ANY(patternDataB); 464 | float distance = 0.0f, val; 465 | int size = PATTERN_SIZE; 466 | float mult = 1.0f; 467 | 468 | while (size > 1) 469 | { 470 | size /= 2; 471 | distance += mult * calcDiff(patternA, patternB, size, 0, size, size); 472 | distance += mult * calcDiff(patternA, patternB, 0, size, size, size); 473 | distance += mult * calcDiff(patternA, patternB, size, size, size, size); 474 | mult *= 2.0f; 475 | } 476 | val = patternA->values[0][0] - patternB->values[0][0]; 477 | distance += mult * val * val; 478 | distance = sqrt(distance); 479 | 480 | PG_RETURN_FLOAT4(distance); 481 | } 482 | 483 | /* 484 | * Distance between signatures: mean-square difference between signatures. 485 | */ 486 | Datum 487 | signature_distance(PG_FUNCTION_ARGS) 488 | { 489 | Signature *signatureA = (Signature *)PG_GETARG_POINTER(0); 490 | Signature *signatureB = (Signature *)PG_GETARG_POINTER(1); 491 | float distance = 0.0f, val; 492 | int i; 493 | 494 | for (i = 0; i < SIGNATURE_SIZE; i++) 495 | { 496 | val = signatureA->values[i] - signatureB->values[i]; 497 | distance += val * val; 498 | } 499 | distance = sqrt(distance); 500 | 501 | PG_RETURN_FLOAT4(distance); 502 | } 503 | 504 | /* 505 | * Make pattern from gd image. 506 | */ 507 | static void 508 | makePattern(gdImagePtr im, PatternData *pattern) 509 | { 510 | int i, j; 511 | for (i = 0; i < PATTERN_SIZE; i++) 512 | for (j = 0; j < PATTERN_SIZE; j++) 513 | { 514 | int pixel = gdImageGetTrueColorPixel(im, i, j); 515 | float red = (float) gdTrueColorGetRed(pixel) / 255.0, 516 | green = (float) gdTrueColorGetGreen(pixel) / 255.0, 517 | blue = (float) gdTrueColorGetBlue(pixel) / 255.0; 518 | pattern->values[i][j] = sqrt((red * red + green * green + blue * blue) / 3.0f); 519 | } 520 | } 521 | 522 | /* 523 | * Normalize pattern: make it minimal value equal to 0 and 524 | * maximum value equal to 1. 525 | */ 526 | static void 527 | normalizePattern(PatternData *pattern) 528 | { 529 | float min = 1.0f, max = 0.0f, val; 530 | int i, j; 531 | for (i = 0; i < PATTERN_SIZE; i++) 532 | { 533 | for (j = 0; j < PATTERN_SIZE; j++) 534 | { 535 | val = pattern->values[i][j]; 536 | if (val < min) min = val; 537 | if (val > max) max = val; 538 | 539 | } 540 | } 541 | for (i = 0; i < PATTERN_SIZE; i++) 542 | { 543 | for (j = 0; j < PATTERN_SIZE; j++) 544 | { 545 | pattern->values[i][j] = (pattern->values[i][j] - min) / (max - min); 546 | } 547 | } 548 | } 549 | 550 | /* 551 | * Do Haar wavelet transform over pattern. 552 | */ 553 | static void 554 | waveletTransform(PatternData *dst, PatternData *src, int size) 555 | { 556 | if (size > 1) 557 | { 558 | int i, j; 559 | size /= 2; 560 | for (i = 0; i < size; i++) 561 | { 562 | for (j = 0; j < size; j++) 563 | { 564 | dst->values[i + size][j] = ( - src->values[2 * i][2 * j] + src->values[2 * i + 1][2 * j] 565 | - src->values[2 * i][2 * j + 1] + src->values[2 * i + 1][2 * j + 1]) / 4.0f; 566 | dst->values[i][j + size] = ( - src->values[2 * i][2 * j] - src->values[2 * i + 1][2 * j] 567 | + src->values[2 * i][2 * j + 1] + src->values[2 * i + 1][2 * j + 1]) / 4.0f; 568 | dst->values[i + size][j + size] = ( src->values[2 * i][2 * j] - src->values[2 * i + 1][2 * j] 569 | - src->values[2 * i][2 * j + 1] + src->values[2 * i + 1][2 * j + 1]) / 4.0f; 570 | } 571 | } 572 | for (i = 0; i < size; i++) 573 | { 574 | for (j = 0; j < size; j++) 575 | { 576 | src->values[i][j] = ( src->values[2 * i][2 * j] + src->values[2 * i + 1][2 * j] 577 | + src->values[2 * i][2 * j + 1] + src->values[2 * i + 1][2 * j + 1]) / 4.0f; 578 | } 579 | } 580 | waveletTransform(dst, src, size); 581 | } 582 | else 583 | { 584 | dst->values[0][0] = src->values[0][0]; 585 | } 586 | } 587 | 588 | /* 589 | * Calculate summary of squares in rectangle "(x, y) - (x + sX, y + sY)". 590 | */ 591 | static float 592 | calcSumm(PatternData *pattern, int x, int y, int sX, int sY) 593 | { 594 | int i, j; 595 | float summ = 0.0f, val; 596 | for (i = x; i < x + sX; i++) 597 | { 598 | for (j = y; j < y + sY; j++) 599 | { 600 | val = pattern->values[i][j]; 601 | summ += val * val; 602 | } 603 | } 604 | return sqrt(summ); 605 | } 606 | 607 | /* 608 | * Make short signature from pattern. 609 | */ 610 | static void 611 | calcSignature(PatternData *pattern, Signature *signature) 612 | { 613 | int size = PATTERN_SIZE / 2; 614 | int i = 0; 615 | float mult = 1.0f; 616 | 617 | while (size > 1) 618 | { 619 | size /= 2; 620 | signature->values[i++] = mult * calcSumm(pattern, size, 0, size, size); 621 | signature->values[i++] = mult * calcSumm(pattern, 0, size, size, size); 622 | signature->values[i++] = mult * calcSumm(pattern, size, size, size, size); 623 | mult *= 2.0f; 624 | } 625 | signature->values[SIGNATURE_SIZE - 1] = pattern->values[0][0]; 626 | } 627 | 628 | #ifdef DEBUG_INFO 629 | 630 | static void 631 | debugPrintPattern(PatternData *pattern, const char *filename, bool color) 632 | { 633 | int i, j; 634 | FILE *out = fopen(filename, "wb"); 635 | for (j = 0; j < PATTERN_SIZE; j++) 636 | { 637 | for (i = 0; i < PATTERN_SIZE; i++) 638 | { 639 | float val = pattern->values[i][j]; 640 | if (!color) 641 | { 642 | fputc((int)(val * 255.999f), out); 643 | fputc((int)(val * 255.999f), out); 644 | fputc((int)(val * 255.999f), out); 645 | } 646 | else 647 | { 648 | if (val >= 0.0f) 649 | { 650 | fputc(0, out); 651 | fputc((int)(val * 255.999f), out); 652 | fputc(0, out); 653 | } 654 | else 655 | { 656 | fputc((int)(- val * 255.999f), out); 657 | fputc(0, out); 658 | fputc(0, out); 659 | } 660 | } 661 | } 662 | } 663 | fclose(out); 664 | } 665 | 666 | static void 667 | debugPrintSignature(Signature *signature, const char *filename) 668 | { 669 | int i; 670 | float max = 0.0f; 671 | FILE *out = fopen(filename, "wb"); 672 | 673 | for (i = 0; i < SIGNATURE_SIZE; i++) 674 | { 675 | max = Max(max, signature->values[i]); 676 | } 677 | 678 | for (i = 0; i < SIGNATURE_SIZE; i++) 679 | { 680 | float val = signature->values[i]; 681 | val = val / max; 682 | fputc((int)(val * 255.999f), out); 683 | fputc((int)(val * 255.999f), out); 684 | fputc((int)(val * 255.999f), out); 685 | } 686 | fclose(out); 687 | } 688 | #endif 689 | -------------------------------------------------------------------------------- /imgsmlr.control: -------------------------------------------------------------------------------- 1 | # imgsmlr extension 2 | comment = 'image similarity module' 3 | default_version = '1.0' 4 | module_pathname = '$libdir/imgsmlr' 5 | relocatable = true 6 | -------------------------------------------------------------------------------- /imgsmlr.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * Image similarity extension 4 | * 5 | * Copyright (c) 2013, PostgreSQL Global Development Group 6 | * 7 | * This software is released under the PostgreSQL Licence. 8 | * 9 | * Author: Alexander Korotkov 10 | * 11 | * IDENTIFICATION 12 | * imgsmlr/imgsmlr.h 13 | *------------------------------------------------------------------------- 14 | */ 15 | #ifndef IMGSMLR_H 16 | #define IMGSMLR_H 17 | 18 | #define PATTERN_SIZE 64 19 | #define SIGNATURE_SIZE 16 20 | 21 | typedef struct 22 | { 23 | float values[PATTERN_SIZE][PATTERN_SIZE]; 24 | } PatternData; 25 | 26 | typedef struct 27 | { 28 | char vl_len_[4]; /* Do not touch this field directly! */ 29 | PatternData data; 30 | } Pattern; 31 | 32 | typedef struct 33 | { 34 | float values[SIGNATURE_SIZE]; 35 | } Signature; 36 | 37 | #define CHECK_SIGNATURE_KEY(key) Assert(VARSIZE_ANY_EXHDR(key) == sizeof(Signature) || VARSIZE_ANY_EXHDR(key) == 2 * sizeof(Signature)); 38 | 39 | #endif /* IMGSMLR_H */ 40 | -------------------------------------------------------------------------------- /imgsmlr_idx.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * Image similarity extension 4 | * 5 | * Copyright (c) 2013, PostgreSQL Global Development Group 6 | * 7 | * This software is released under the PostgreSQL Licence. 8 | * 9 | * Author: Alexander Korotkov 10 | * 11 | * IDENTIFICATION 12 | * imgsmlr/imgsmlr_idx.c 13 | *------------------------------------------------------------------------- 14 | */ 15 | #include "postgres.h" 16 | #include "fmgr.h" 17 | #include "imgsmlr.h" 18 | #include "access/gist.h" 19 | #include "access/gist_private.h" 20 | #include "access/skey.h" 21 | #include "c.h" 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | PG_FUNCTION_INFO_V1(signature_consistent); 28 | PG_FUNCTION_INFO_V1(signature_compress); 29 | PG_FUNCTION_INFO_V1(signature_decompress); 30 | PG_FUNCTION_INFO_V1(signature_penalty); 31 | PG_FUNCTION_INFO_V1(signature_picksplit); 32 | PG_FUNCTION_INFO_V1(signature_union); 33 | PG_FUNCTION_INFO_V1(signature_same); 34 | PG_FUNCTION_INFO_V1(signature_gist_distance); 35 | 36 | Datum signature_consistent(PG_FUNCTION_ARGS); 37 | Datum signature_compress(PG_FUNCTION_ARGS); 38 | Datum signature_decompress(PG_FUNCTION_ARGS); 39 | Datum signature_penalty(PG_FUNCTION_ARGS); 40 | Datum signature_picksplit(PG_FUNCTION_ARGS); 41 | Datum signature_union(PG_FUNCTION_ARGS); 42 | Datum signature_same(PG_FUNCTION_ARGS); 43 | Datum signature_gist_distance(PG_FUNCTION_ARGS); 44 | 45 | static void set_signature(Signature *dst, bytea *src); 46 | static void extend_signature(Signature *dst, bytea *srcBytea); 47 | static void union_intersect_size(bytea *dstBytea, bytea *srcBytea, float *unionSize, float *intersectSize); 48 | static float key_size(bytea *key); 49 | 50 | Datum 51 | signature_compress(PG_FUNCTION_ARGS) 52 | { 53 | GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); 54 | bytea *res; 55 | 56 | if (entry->leafkey) 57 | { 58 | GISTENTRY *retval; 59 | 60 | res = (bytea *)palloc(sizeof(Signature) + VARHDRSZ); 61 | SET_VARSIZE(res, sizeof(Signature) + VARHDRSZ); 62 | memcpy(VARDATA(res), DatumGetPointer(entry->key), sizeof(Signature)); 63 | 64 | retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); 65 | gistentryinit(*retval, PointerGetDatum(res), 66 | entry->rel, entry->page, 67 | entry->offset, FALSE); 68 | 69 | PG_RETURN_POINTER(retval); 70 | } 71 | else 72 | { 73 | PG_RETURN_POINTER(entry); 74 | } 75 | } 76 | 77 | Datum 78 | signature_decompress(PG_FUNCTION_ARGS) 79 | { 80 | GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); 81 | bytea *key = DatumGetByteaP(PG_DETOAST_DATUM(entry->key)); 82 | 83 | if (key != DatumGetByteaP(entry->key)) 84 | { 85 | GISTENTRY *retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); 86 | 87 | gistentryinit(*retval, PointerGetDatum(key), 88 | entry->rel, entry->page, 89 | entry->offset, FALSE); 90 | PG_RETURN_POINTER(retval); 91 | } 92 | PG_RETURN_POINTER(entry); 93 | } 94 | 95 | Datum 96 | signature_consistent(PG_FUNCTION_ARGS) 97 | { 98 | bool *recheck = (bool *) PG_GETARG_POINTER(4); 99 | *recheck = true; 100 | 101 | PG_RETURN_BOOL(true); 102 | } 103 | 104 | static void 105 | set_signature(Signature *dst, bytea *src) 106 | { 107 | CHECK_SIGNATURE_KEY(src); 108 | 109 | memcpy(dst, VARDATA_ANY(src), sizeof(Signature)); 110 | if (VARSIZE_ANY_EXHDR(src) == sizeof(Signature)) 111 | memcpy(dst + 1, VARDATA_ANY(src), sizeof(Signature)); 112 | else 113 | memcpy(dst + 1, VARDATA_ANY(src) + sizeof(Signature), sizeof(Signature)); 114 | } 115 | 116 | static void 117 | extend_signature(Signature *dst, bytea *srcBytea) 118 | { 119 | Signature *src = (Signature *)VARDATA_ANY(srcBytea); 120 | int i; 121 | 122 | CHECK_SIGNATURE_KEY(srcBytea); 123 | 124 | for (i = 0; i < SIGNATURE_SIZE; i++) 125 | dst->values[i] = Min(dst->values[i], src->values[i]); 126 | 127 | dst++; 128 | if (VARSIZE_ANY_EXHDR(srcBytea) == 2 * sizeof(Signature)) 129 | src++; 130 | 131 | for (i = 0; i < SIGNATURE_SIZE; i++) 132 | dst->values[i] = Max(dst->values[i], src->values[i]); 133 | } 134 | 135 | Datum 136 | signature_union(PG_FUNCTION_ARGS) 137 | { 138 | GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); 139 | int *sizep = (int *) PG_GETARG_POINTER(1); 140 | int i; 141 | bytea *out; 142 | Signature *outSignature; 143 | 144 | out = (bytea *)palloc(2 * sizeof(Signature) + VARHDRSZ); 145 | SET_VARSIZE(out, 2 * sizeof(Signature) + VARHDRSZ); 146 | outSignature = (Signature *)VARDATA(out); 147 | set_signature(outSignature, DatumGetByteaP(entryvec->vector[0].key)); 148 | 149 | for (i = 1; i < entryvec->n; i++) 150 | { 151 | extend_signature(outSignature, DatumGetByteaP(entryvec->vector[i].key)); 152 | } 153 | 154 | *sizep = VARSIZE(out); 155 | PG_RETURN_POINTER(out); 156 | } 157 | 158 | Datum 159 | signature_same(PG_FUNCTION_ARGS) 160 | { 161 | bytea *b1 = PG_GETARG_BYTEA_P(0); 162 | bytea *b2 = PG_GETARG_BYTEA_P(1); 163 | bool *result = (bool *) PG_GETARG_POINTER(2); 164 | 165 | Assert(VARSIZE_ANY_EXHDR(b1) == sizeof(Signature) || 166 | VARSIZE_ANY_EXHDR(b1) == 2 * sizeof(Signature)); 167 | Assert(VARSIZE_ANY_EXHDR(b2) == sizeof(Signature) || 168 | VARSIZE_ANY_EXHDR(b2) == 2 * sizeof(Signature)); 169 | 170 | if (VARSIZE_ANY_EXHDR(b1) == VARSIZE_ANY_EXHDR(b2)) 171 | { 172 | *result = (memcpy(b1, b2, VARSIZE_ANY_EXHDR(b1)) == 0); 173 | } 174 | else 175 | { 176 | *result = false; 177 | } 178 | 179 | PG_RETURN_POINTER(result); 180 | } 181 | 182 | Datum 183 | signature_penalty(PG_FUNCTION_ARGS) 184 | { 185 | GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); 186 | GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); 187 | float *result = (float *) PG_GETARG_POINTER(2); 188 | float intersect_size, union_size; 189 | 190 | union_intersect_size( 191 | DatumGetByteaP(origentry->key), 192 | DatumGetByteaP(newentry->key), 193 | &union_size, 194 | &intersect_size); 195 | 196 | *result = union_size - key_size(DatumGetByteaP(origentry->key)); 197 | 198 | PG_RETURN_FLOAT8(*result); 199 | } 200 | 201 | static void 202 | union_intersect_size(bytea *dstBytea, bytea *srcBytea, float *unionSize, float *intersectSize) 203 | { 204 | Signature *dstMin = (Signature *)VARDATA_ANY(dstBytea), *dstMax; 205 | Signature *srcMin = (Signature *)VARDATA_ANY(srcBytea), *srcMax; 206 | float unionSizeAccum = 1.0f, intersectSizeAccum = 1.0f; 207 | int i; 208 | 209 | CHECK_SIGNATURE_KEY(dstBytea); 210 | CHECK_SIGNATURE_KEY(srcBytea); 211 | 212 | srcMax = srcMin; 213 | if (VARSIZE_ANY_EXHDR(srcBytea) == 2 * sizeof(Signature)) 214 | srcMax++; 215 | 216 | dstMax = dstMin; 217 | if (VARSIZE_ANY_EXHDR(dstBytea) == 2 * sizeof(Signature)) 218 | dstMax++; 219 | 220 | for (i = 0; i < SIGNATURE_SIZE; i++) 221 | { 222 | float unionRange = Max(dstMax->values[i], srcMax->values[i]) - 223 | Min(dstMin->values[i], srcMin->values[i]); 224 | float intersectRange = Min(dstMax->values[i], srcMax->values[i]) - 225 | Max(dstMin->values[i], srcMin->values[i]); 226 | unionSizeAccum *= unionRange; 227 | if (intersectRange < 0.0f) 228 | intersectRange = 0.0f; 229 | intersectSizeAccum *= intersectRange; 230 | } 231 | 232 | *unionSize = unionSizeAccum; 233 | *intersectSize = intersectSizeAccum; 234 | } 235 | 236 | static float 237 | key_size(bytea *key) 238 | { 239 | Signature *keyMin = (Signature *)VARDATA_ANY(key), *keyMax; 240 | float size = 1.0f; 241 | int i; 242 | 243 | CHECK_SIGNATURE_KEY(key); 244 | 245 | keyMax = keyMin; 246 | if (VARSIZE_ANY_EXHDR(key) == 2 * sizeof(Signature)) 247 | keyMax++; 248 | 249 | for (i = 0; i < SIGNATURE_SIZE; i++) 250 | { 251 | float range = keyMax->values[i] - keyMin->values[i]; 252 | size *= range; 253 | } 254 | return size; 255 | } 256 | 257 | Datum 258 | signature_picksplit(PG_FUNCTION_ARGS) 259 | { 260 | GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); 261 | GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); 262 | OffsetNumber i, 263 | j; 264 | bytea *datum_alpha, 265 | *datum_beta; 266 | bytea *datum_l, 267 | *datum_r; 268 | bool firsttime; 269 | float size_waste, 270 | waste; 271 | float size_l, 272 | size_r; 273 | int nbytes; 274 | OffsetNumber seed_1 = 1, 275 | seed_2 = 2; 276 | OffsetNumber *left, 277 | *right; 278 | OffsetNumber maxoff; 279 | Signature *signature_l, *signature_r; 280 | bool *distributed; 281 | int undistributed_count; 282 | 283 | /* 284 | * fprintf(stderr, "picksplit\n"); 285 | */ 286 | maxoff = entryvec->n - 1; 287 | nbytes = (maxoff + 1) * sizeof(OffsetNumber); 288 | distributed = (bool *)palloc0(sizeof(bool) * (maxoff + 1)); 289 | v->spl_left = (OffsetNumber *) palloc(nbytes); 290 | v->spl_right = (OffsetNumber *) palloc(nbytes); 291 | 292 | firsttime = true; 293 | waste = 0.0; 294 | 295 | for (i = FirstOffsetNumber; i < maxoff; i = OffsetNumberNext(i)) 296 | { 297 | datum_alpha = DatumGetByteaP(entryvec->vector[i].key); 298 | for (j = OffsetNumberNext(i); j <= maxoff; j = OffsetNumberNext(j)) 299 | { 300 | float unionSize, intersectSize; 301 | 302 | datum_beta = DatumGetByteaP(entryvec->vector[j].key); 303 | 304 | union_intersect_size(datum_alpha, datum_beta, 305 | &unionSize, &intersectSize); 306 | 307 | size_waste = unionSize - intersectSize; 308 | 309 | /* 310 | * are these a more promising split than what we've already seen? 311 | */ 312 | 313 | if (size_waste > waste || firsttime) 314 | { 315 | waste = size_waste; 316 | seed_1 = i; 317 | seed_2 = j; 318 | firsttime = false; 319 | } 320 | } 321 | } 322 | 323 | left = v->spl_left; 324 | v->spl_nleft = 0; 325 | right = v->spl_right; 326 | v->spl_nright = 0; 327 | 328 | datum_l = (bytea *)palloc(2 * sizeof(Signature) + VARHDRSZ); 329 | SET_VARSIZE(datum_l, 2 * sizeof(Signature) + VARHDRSZ); 330 | signature_l = (Signature *)VARDATA(datum_l); 331 | set_signature(signature_l, DatumGetByteaP(entryvec->vector[seed_1].key)); 332 | size_l = key_size(datum_l); 333 | 334 | datum_r = (bytea *)palloc(2 * sizeof(Signature) + VARHDRSZ); 335 | SET_VARSIZE(datum_r, 2 * sizeof(Signature) + VARHDRSZ); 336 | signature_r = (Signature *)VARDATA(datum_r); 337 | set_signature(signature_r, DatumGetByteaP(entryvec->vector[seed_2].key)); 338 | size_r = key_size(datum_r); 339 | 340 | distributed[seed_1] = true; 341 | *left++ = seed_1; 342 | v->spl_nleft++; 343 | 344 | distributed[seed_2] = true; 345 | *right++ = seed_2; 346 | v->spl_nright++; 347 | undistributed_count = maxoff - 2; 348 | 349 | /* 350 | * Now split up the regions between the two seeds. An important property 351 | * of this split algorithm is that the split vector v has the indices of 352 | * items to be split in order in its left and right vectors. We exploit 353 | * this property by doing a merge in the code that actually splits the 354 | * page. 355 | * 356 | * For efficiency, we also place the new index tuple in this loop. This is 357 | * handled at the very end, when we have placed all the existing tuples 358 | * and i == maxoff + 1. 359 | */ 360 | 361 | while (undistributed_count > 0) 362 | { 363 | OffsetNumber selected = InvalidOffsetNumber; 364 | float max_delta = 0.0; 365 | 366 | firsttime = true; 367 | 368 | for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) 369 | { 370 | float union_l, union_r, intersect_l, intersect_r, delta; 371 | bool direction = false; 372 | 373 | if (distributed[i]) 374 | continue; 375 | 376 | /*memcpy(signature_tmp, signature_l, sizeof(Signature) * 2); 377 | extend_signature(signature_tmp, DatumGetByteaP(entryvec->vector[i].key)); 378 | union_intersect_size(tmp, datum_r, &union_l, &intersect_l); 379 | 380 | memcpy(signature_tmp, signature_r, sizeof(Signature) * 2); 381 | extend_signature(signature_tmp, DatumGetByteaP(entryvec->vector[i].key)); 382 | union_intersect_size(tmp, datum_l, &union_r, &intersect_r); 383 | 384 | delta = intersect_l - intersect_r;*/ 385 | 386 | union_intersect_size(datum_l, DatumGetByteaP(entryvec->vector[i].key), &union_l, &intersect_l); 387 | union_intersect_size(datum_r, DatumGetByteaP(entryvec->vector[i].key), &union_r, &intersect_r); 388 | 389 | delta = union_l - size_l - union_r + size_r; 390 | 391 | if (v->spl_nleft < v->spl_nright && delta < 0) 392 | direction = true; 393 | if (v->spl_nleft > v->spl_nright && delta > 0) 394 | direction = true; 395 | 396 | if (fabs(delta) > fabs(max_delta) || 397 | (fabs(delta) == fabs(max_delta) && direction) || 398 | firsttime) 399 | { 400 | max_delta = delta; 401 | selected = i; 402 | firsttime = false; 403 | } 404 | } 405 | Assert(OffsetNumberIsValid(selected)); 406 | 407 | /* pick which page to add it to */ 408 | if (max_delta < 0 || (max_delta == 0 && v->spl_nleft < v->spl_nright)) 409 | { 410 | extend_signature(signature_l, DatumGetByteaP(entryvec->vector[selected].key)); 411 | size_l = key_size(datum_l); 412 | *left++ = selected; 413 | v->spl_nleft++; 414 | } 415 | else 416 | { 417 | extend_signature(signature_r, DatumGetByteaP(entryvec->vector[selected].key)); 418 | size_r = key_size(datum_r); 419 | *right++ = selected; 420 | v->spl_nright++; 421 | } 422 | distributed[selected] = true; 423 | undistributed_count--; 424 | } 425 | *left = *right = FirstOffsetNumber; /* sentinel value, see dosplit() */ 426 | 427 | v->spl_ldatum = PointerGetDatum(datum_l); 428 | v->spl_rdatum = PointerGetDatum(datum_r); 429 | 430 | PG_RETURN_POINTER(v); 431 | } 432 | 433 | Datum 434 | signature_gist_distance(PG_FUNCTION_ARGS) 435 | { 436 | GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); 437 | /* StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);*/ 438 | Signature *arg = (Signature *)PG_GETARG_POINTER(1), *keyMin, *keyMax; 439 | bytea *key = DatumGetByteaP(entry->key); 440 | double distance = 0.0; 441 | int i = 0; 442 | 443 | CHECK_SIGNATURE_KEY(key); 444 | 445 | keyMin = (Signature *)VARDATA_ANY(key); 446 | keyMax = keyMin; 447 | if (VARSIZE_ANY_EXHDR(key) == 2 * sizeof(Signature)) 448 | keyMax++; 449 | 450 | for (i = 0; i < SIGNATURE_SIZE; i++) 451 | { 452 | if (arg->values[i] < keyMin->values[i]) 453 | distance += (arg->values[i] - keyMin->values[i]) * (arg->values[i] - keyMin->values[i]); 454 | if (arg->values[i] > keyMax->values[i]) 455 | distance += (arg->values[i] - keyMax->values[i]) * (arg->values[i] - keyMax->values[i]); 456 | } 457 | 458 | PG_RETURN_FLOAT8(sqrt(distance)); 459 | } 460 | -------------------------------------------------------------------------------- /sql/imgsmlr.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION imgsmlr; 2 | 3 | CREATE TABLE image (id integer PRIMARY KEY, data bytea); 4 | CREATE TABLE tmp (data text); 5 | 6 | \copy tmp from 'data/1.jpg.hex' 7 | INSERT INTO image VALUES (1, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 8 | TRUNCATE tmp; 9 | \copy tmp from 'data/2.png.hex' 10 | INSERT INTO image VALUES (2, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 11 | TRUNCATE tmp; 12 | \copy tmp from 'data/3.gif.hex' 13 | INSERT INTO image VALUES (3, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 14 | TRUNCATE tmp; 15 | \copy tmp from 'data/4.jpg.hex' 16 | INSERT INTO image VALUES (4, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 17 | TRUNCATE tmp; 18 | \copy tmp from 'data/5.png.hex' 19 | INSERT INTO image VALUES (5, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 20 | TRUNCATE tmp; 21 | \copy tmp from 'data/6.gif.hex' 22 | INSERT INTO image VALUES (6, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 23 | TRUNCATE tmp; 24 | \copy tmp from 'data/7.jpg.hex' 25 | INSERT INTO image VALUES (7, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 26 | TRUNCATE tmp; 27 | \copy tmp from 'data/8.png.hex' 28 | INSERT INTO image VALUES (8, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 29 | TRUNCATE tmp; 30 | \copy tmp from 'data/9.gif.hex' 31 | INSERT INTO image VALUES (9, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 32 | TRUNCATE tmp; 33 | \copy tmp from 'data/10.jpg.hex' 34 | INSERT INTO image VALUES (10, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 35 | TRUNCATE tmp; 36 | \copy tmp from 'data/11.png.hex' 37 | INSERT INTO image VALUES (11, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 38 | TRUNCATE tmp; 39 | \copy tmp from 'data/12.gif.hex' 40 | INSERT INTO image VALUES (12, (SELECT decode(string_agg(data, ''), 'hex') FROM tmp)); 41 | TRUNCATE tmp; 42 | 43 | CREATE TABLE pat AS ( 44 | SELECT 45 | id, 46 | shuffle_pattern(pattern)::text::pattern AS pattern, 47 | pattern2signature(pattern)::text::signature AS signature 48 | FROM ( 49 | SELECT 50 | id, 51 | (CASE WHEN id % 3 = 1 THEN jpeg2pattern(data) 52 | WHEN id % 3 = 2 THEN png2pattern(data) 53 | WHEN id % 3 = 0 THEN gif2pattern(data) 54 | ELSE NULL END) AS pattern 55 | FROM 56 | image 57 | ) x 58 | ); 59 | 60 | ALTER TABLE pat ADD PRIMARY KEY (id); 61 | CREATE INDEX pat_signature_idx ON pat USING gist (signature); 62 | 63 | SELECT p1.id, p2.id, round((p1.pattern <-> p2.pattern)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id; 64 | SELECT p1.id, p2.id, round((p1.signature <-> p2.signature)::numeric, 4) FROM pat p1, pat p2 ORDER BY p1.id, p2.id; 65 | 66 | SET enable_seqscan = OFF; 67 | 68 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 1) LIMIT 3; 69 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 4) LIMIT 3; 70 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 7) LIMIT 3; 71 | SELECT id FROM pat ORDER BY signature <-> (SELECT signature FROM pat WHERE id = 10) LIMIT 3; 72 | -------------------------------------------------------------------------------- /travis/dep-ubuntu-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat ./travis/llvm-snapshot.gpg.key | sudo apt-key add - 4 | echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-$(lsb_release -cs)-$LLVM_VER main" | sudo tee /etc/apt/sources.list.d/llvm.list 5 | -------------------------------------------------------------------------------- /travis/dep-ubuntu-postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat ./travis/postgresql.gpg.key | sudo apt-key add - 4 | echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PG_VER" | sudo tee /etc/apt/sources.list.d/pgdg.list 5 | -------------------------------------------------------------------------------- /travis/llvm-snapshot.gpg.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1.4.12 (GNU/Linux) 3 | 4 | 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 50 | bXb5c6gCHESH5PXwPU4jQEE7Ib9J6sbk7ZT2Mw== 51 | =j+4q 52 | -----END PGP PUBLIC KEY BLOCK----- 53 | -------------------------------------------------------------------------------- /travis/pg-travis-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | echo -en 'travis_fold:start:pg_install\\r' && echo 'PostgreSQL installation' 6 | 7 | sudo apt-get update 8 | 9 | # bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de 10 | sudo update-alternatives --remove-all postmaster.1.gz 11 | 12 | # stop all existing instances (because of https://github.com/travis-ci/travis-cookbooks/pull/221) 13 | sudo service postgresql stop 14 | # ... and make sure they don't come back 15 | echo 'exit 0' | sudo tee /etc/init.d/postgresql 16 | sudo chmod a+x /etc/init.d/postgresql 17 | 18 | # install PostgreSQL 19 | if [ $CHECK_TYPE = "valgrind" ]; then 20 | # install required packages 21 | apt_packages="build-essential libgd-dev valgrind lcov" 22 | sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y install -qq $apt_packages 23 | # grab sources from github 24 | tag=`curl -s 'https://api.github.com/repos/postgres/postgres/git/refs/tags' | jq -r '.[] | .ref' | sed 's/^refs\/tags\///' | grep "REL_*${PG_VER/./_}_" | tail -n 1` 25 | prefix="$HOME/pgsql-$tag" 26 | curl "https://codeload.github.com/postgres/postgres/tar.gz/$tag" -o ~/$tag.tar.gz 27 | # build them with valgrind support 28 | pushd ~ 29 | tar -xzf "$tag.tar.gz" 30 | cd "postgres-$tag" 31 | ./configure --enable-debug --enable-cassert --enable-coverage --prefix=$prefix 32 | sed -i.bak "s/\/* #define USE_VALGRIND *\//#define USE_VALGRIND/g" src/include/pg_config_manual.h 33 | make -sj4 34 | make -sj4 install 35 | popd 36 | export PATH="$prefix/bin:$PATH" 37 | else 38 | apt_packages="postgresql-$PG_VER postgresql-server-dev-$PG_VER postgresql-common build-essential libgd-dev" 39 | sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y install -qq $apt_packages 40 | prefix=/usr/lib/postgresql/$PG_VER 41 | fi 42 | 43 | # config path 44 | pg_ctl_path=$prefix/bin/pg_ctl 45 | initdb_path=$prefix/bin/initdb 46 | config_path=$prefix/bin/pg_config 47 | 48 | # exit code 49 | status=0 50 | 51 | echo -en 'travis_fold:end:pg_install\\r' 52 | 53 | # perform code analysis if necessary 54 | if [ $CHECK_TYPE = "static" ]; then 55 | echo -en 'travis_fold:start:static_analysis\\r' && echo 'Static analysis' 56 | 57 | if [ "$CC" = "clang" ]; then 58 | sudo apt-get -y install -qq clang-$LLVM_VER 59 | 60 | scan-build-$LLVM_VER --status-bugs \ 61 | make USE_PGXS=1 USE_ASSERT_CHECKING=1 PG_CONFIG=$config_path || status=$? 62 | 63 | elif [ "$CC" = "gcc" ]; then 64 | sudo apt-get -y install -qq cppcheck 65 | 66 | cppcheck --template "{file} ({line}): {severity} ({id}): {message}" \ 67 | --enable=warning,portability,performance \ 68 | --suppress=redundantAssignment \ 69 | --suppress=uselessAssignmentPtrArg \ 70 | --suppress=incorrectStringBooleanError \ 71 | --std=c89 *.c *.h 2> cppcheck.log 72 | 73 | if [ -s cppcheck.log ]; then 74 | cat cppcheck.log 75 | status=1 # error 76 | fi 77 | fi 78 | 79 | # don't forget to "make clean" 80 | make clean USE_PGXS=1 PG_CONFIG=$config_path 81 | echo -en 'travis_fold:end:static_analysis\\r' 82 | exit $status 83 | fi 84 | 85 | echo -en 'travis_fold:start:build_extension\\r' && echo 'Build extension' 86 | 87 | # build extension (using CFLAGS_SL for gcov) 88 | if [ $CHECK_TYPE == "valgrind" ]; then 89 | make USE_PGXS=1 USE_ASSERT_CHECKING=1 PG_CONFIG=$config_path 90 | make install USE_PGXS=1 PG_CONFIG=$config_path 91 | else 92 | make USE_PGXS=1 USE_ASSERT_CHECKING=1 CC=$CC PG_CONFIG=$config_path CFLAGS_SL="$($config_path --cflags_sl) -coverage" 93 | sudo make install USE_PGXS=1 PG_CONFIG=$config_path 94 | fi 95 | 96 | echo -en 'travis_fold:end:build_extension\\r' 97 | 98 | echo -en 'travis_fold:start:run_tests\\r' && echo 'Run tests' 99 | 100 | # enable core dumps and specify their path 101 | ulimit -c unlimited -S 102 | echo '/tmp/%e-%s-%p.core' | sudo tee /proc/sys/kernel/core_pattern 103 | 104 | # set permission to write postgres locks 105 | sudo chown $USER /var/run/postgresql/ 106 | 107 | # create cluster 'test' 108 | CLUSTER_PATH=$(pwd)/test_cluster 109 | $initdb_path -D $CLUSTER_PATH -U $USER -A trust 110 | 111 | # start cluster 'test' 112 | echo "port = 55435" >> $CLUSTER_PATH/postgresql.conf 113 | if [ $CHECK_TYPE = "valgrind" ]; then 114 | PGCTLTIMEOUT=600 \ 115 | valgrind --leak-check=no --gen-suppressions=all \ 116 | --suppressions=$HOME/postgres-$tag/src/tools/valgrind.supp --time-stamp=yes \ 117 | --log-file=/tmp/pid-%p.log --trace-children=yes \ 118 | $pg_ctl_path -D $CLUSTER_PATH start -l postgres.log -w 119 | else 120 | $pg_ctl_path -D $CLUSTER_PATH start -l postgres.log -w 121 | fi 122 | 123 | # run regression tests 124 | PGPORT=55435 PGUSER=$USER PG_CONFIG=$config_path make installcheck USE_PGXS=1 || status=$? 125 | 126 | # stop cluster 127 | $pg_ctl_path -D $CLUSTER_PATH stop -l postgres.log -w 128 | 129 | echo -en 'travis_fold:end:run_tests\\r' 130 | 131 | echo -en 'travis_fold:start:output\\r' && echo 'Check output' 132 | 133 | # show diff if it exists 134 | if test -f regression.diffs; then cat regression.diffs; fi 135 | 136 | # show valgrind logs if needed 137 | if [ $CHECK_TYPE = "valgrind" ]; then 138 | for f in ` find /tmp -name pid-*.log ` ; do 139 | if grep -q 'Command: [^ ]*/postgres' $f && grep -q 'ERROR SUMMARY: [1-9]' $f; then 140 | echo "========= Contents of $f" 141 | cat $f 142 | status=1 143 | fi 144 | done 145 | fi 146 | 147 | # check core dumps if any 148 | for corefile in $(find /tmp/ -name '*.core' 2>/dev/null) ; do 149 | binary=$(gdb -quiet -core $corefile -batch -ex 'info auxv' | grep AT_EXECFN | perl -pe "s/^.*\"(.*)\"\$/\$1/g") 150 | echo dumping $corefile for $binary 151 | gdb --batch --quiet -ex "thread apply all bt full" -ex "quit" $binary $corefile 152 | done 153 | 154 | echo -en 'travis_fold:end:output\\r' 155 | 156 | echo -en 'travis_fold:start:coverage\\r' && echo 'Coverage check' 157 | 158 | #generate *.gcov files 159 | if [ $CC = "clang" ]; then 160 | bash <(curl -s https://codecov.io/bash) -x "llvm-cov gcov" 161 | else 162 | bash <(curl -s https://codecov.io/bash) 163 | fi 164 | 165 | echo -en 'travis_fold:end:coverage\\r' 166 | 167 | exit $status 168 | -------------------------------------------------------------------------------- /travis/postgresql.gpg.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | 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 76 | =DA1T 77 | -----END PGP PUBLIC KEY BLOCK----- 78 | --------------------------------------------------------------------------------