Update all new Tasks
This commit is contained in:
parent
00a190b0a6
commit
91df62d461
5697 changed files with 93386 additions and 804 deletions
94
Task/Zhang-Suen-thinning-algorithm/00DESCRIPTION
Normal file
94
Task/Zhang-Suen-thinning-algorithm/00DESCRIPTION
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
This is an algorithm used to thin a black and white i.e. one bit per pixel images.
|
||||
|
||||
For example, with an input image of:
|
||||
<pre>
|
||||
################# #############
|
||||
################## ################
|
||||
################### ##################
|
||||
######## ####### ###################
|
||||
###### ####### ####### ######
|
||||
###### ####### #######
|
||||
################# #######
|
||||
################ #######
|
||||
################# #######
|
||||
###### ####### #######
|
||||
###### ####### #######
|
||||
###### ####### ####### ######
|
||||
######## ####### ###################
|
||||
######## ####### ###### ################## ######
|
||||
######## ####### ###### ################ ######
|
||||
######## ####### ###### ############# ######
|
||||
</pre>
|
||||
It produces the thinned output:
|
||||
<pre>
|
||||
|
||||
# ########## #######
|
||||
## # #### #
|
||||
# # ##
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
############ #
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
# ##
|
||||
# ############
|
||||
### ###
|
||||
|
||||
</pre>
|
||||
|
||||
;Algorithm:
|
||||
Assume black pixels are one and white pixels zero, and that the input image is a rectangular N by M array of ones and zeroes.
|
||||
|
||||
The algorithm operates on all black pixels P1 that can have eight neighbours. The neighbours are, in order, arranged as:
|
||||
<table border="1">
|
||||
<tr><td>P9</td><td>P2</td><td>P3</td></tr>
|
||||
<tr><td>P8</td><td><b>P1</b></td><td>P4</td></tr>
|
||||
<tr><td>P7</td><td>P6</td><td>P5</td></tr>
|
||||
</table>
|
||||
|
||||
Obviously the boundary pixels of the image cannot have the full eight neighbours.
|
||||
|
||||
* Define <math>A(P1)</math> = the number of transitions from white to black, (0 -> 1) in the sequence P2,P3,P4,P5,P6,P7,P8,P9,P2. (Note the extra P2 at the end - it is circular).
|
||||
* Define <math>B(P1)</math> = The number of black pixel neighbours of P1. ( = sum(P2 .. P9) )
|
||||
|
||||
;Step 1:
|
||||
All pixels are tested and pixels satisfying all the following conditions (simultaneously) are just noted at this stage.
|
||||
* (0) The pixel is black and has eight neighbours
|
||||
* (1) <math>2 <= B(P1) <= 6</math>
|
||||
* (2) A(P1) = 1
|
||||
* (3) At least one of P2 and P4 and P6 is white
|
||||
* (4) At least one of P4 and P6 and P8 is white
|
||||
After iterating over the image and collecting all the pixels satisfying all step 1 conditions, all these condition satisfying pixels are set to white.
|
||||
|
||||
;Step 2:
|
||||
All pixels are again tested and pixels satisfying all the following conditions are just noted at this stage.
|
||||
* (0) The pixel is black and has eight neighbours
|
||||
* (1) <math>2 <= B(P1) <= 6</math>
|
||||
* (2) A(P1) = 1
|
||||
* (3) At least one of P2 and P4 and '''P8''' is white
|
||||
* (4) At least one of '''P2''' and P6 and P8 is white
|
||||
After iterating over the image and collecting all the pixels satisfying all step 2 conditions, all these condition satisfying pixels are again set to white.
|
||||
|
||||
;Iteration:
|
||||
If any pixels were set in this round of either step 1 or step 2 then all steps are repeated until no image pixels are so changed.
|
||||
|
||||
;Task:
|
||||
# Write a routine to perform Zhang-Suen thinning on an image matrix of ones and zeroes.
|
||||
# Use the routine to thin the following image and show the output here on this page as either a matrix of ones and zeroes, an image, or an ASCII-art image of space/non-space characters.
|
||||
<pre>00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000</pre>
|
||||
|
||||
;Reference:
|
||||
* [http://nayefreza.wordpress.com/2013/05/11/zhang-suen-thinning-algorithm-java-implementation/ Zhang-Suen Thinning Algorithm, Java Implementation] by Nayef Reza.
|
||||
* "Character Recognition Systems: A Guide for Students and Practitioners" By Mohamed Cheriet, Nawwaf Kharma, Cheng-Lin Liu, Ching Suen
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
FileIn := A_ScriptDir "\Zhang-Suen.txt"
|
||||
FileOut := A_ScriptDir "\NewFile.txt"
|
||||
|
||||
if (!FileExist(FileIn)) {
|
||||
MsgBox, 48, File Not Found, % "File """ FileIn """ not found."
|
||||
ExitApp
|
||||
}
|
||||
S := {}
|
||||
N := [2,3,4,5,6,7,8,9,2]
|
||||
|
||||
Loop, Read, % FileIn
|
||||
{
|
||||
LineNum := A_Index
|
||||
Loop, Parse, A_LoopReadLine
|
||||
S[LineNum, A_Index] := A_LoopField
|
||||
}
|
||||
|
||||
Loop {
|
||||
FlipCount := 0
|
||||
Loop, 2 {
|
||||
Noted := [], i := A_Index
|
||||
for LineNum, Line in S {
|
||||
for PixNum, Pix in Line {
|
||||
; (0)
|
||||
if (Pix = 0 || (P := GetNeighbors(LineNum, PixNum, S)) = 1)
|
||||
continue
|
||||
; (1)
|
||||
BP := 0
|
||||
for j, Val in P
|
||||
BP += Val
|
||||
if (BP < 2 || BP > 6)
|
||||
continue
|
||||
; (2)
|
||||
AP := 0
|
||||
Loop, 8
|
||||
if (P[N[A_Index]] = "0" && P[N[A_Index + 1]] = "1")
|
||||
AP++
|
||||
if (AP != 1)
|
||||
continue
|
||||
; (3 and 4)
|
||||
if (i = 1) {
|
||||
if (P[2] + P[4] + P[6] = 3 || P[4] + P[6] + P[8] = 3)
|
||||
continue
|
||||
}
|
||||
else if (P[2] + P[4] + P[8] = 3 || P[2] + P[6] + P[8] = 3)
|
||||
continue
|
||||
|
||||
Noted.Insert([LineNum, PixNum])
|
||||
FlipCount++
|
||||
}
|
||||
}
|
||||
for j, Coords in Noted
|
||||
S[Coords[1], Coords[2]] := 0
|
||||
}
|
||||
if (!FlipCount)
|
||||
break
|
||||
}
|
||||
|
||||
for LineNum, Line in S {
|
||||
for PixNum, Pix in Line
|
||||
Out .= Pix ? "#" : " "
|
||||
Out .= "`n"
|
||||
}
|
||||
FileAppend, % Out, % FileOut
|
||||
|
||||
GetNeighbors(Y, X, S) {
|
||||
Neighbors := []
|
||||
if ((Neighbors[8] := S[Y, X - 1]) = "")
|
||||
return 1
|
||||
if ((Neighbors[4] := S[Y, X + 1]) = "")
|
||||
return 1
|
||||
Loop, 3
|
||||
if ((Neighbors[A_Index = 1 ? 9 : A_Index] := S[Y - 1, X - 2 + A_Index]) = "")
|
||||
return 1
|
||||
Loop, 3
|
||||
if ((Neighbors[8 - A_Index] := S[Y + 1, X - 2 + A_Index]) = "")
|
||||
return 1
|
||||
return Neighbors
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import std.stdio, std.algorithm, std.string, std.functional,
|
||||
std.typecons, std.typetuple, bitmap;
|
||||
|
||||
struct BlackWhite {
|
||||
ubyte c;
|
||||
alias c this;
|
||||
static immutable black = typeof(this)(0),
|
||||
white = typeof(this)(1);
|
||||
}
|
||||
|
||||
alias Neighbours = BlackWhite[9];
|
||||
alias Img = Image!BlackWhite;
|
||||
|
||||
/// Zhang-Suen thinning algorithm.
|
||||
Img zhangSuen(Img image1, Img image2) pure nothrow @safe @nogc
|
||||
in {
|
||||
assert(image1.image.all!(x => x == Img.black || x == Img.white));
|
||||
assert(image1.nx == image2.nx && image1.ny == image2.ny);
|
||||
} out(result) {
|
||||
assert(result.nx == image1.nx && result.ny == image1.ny);
|
||||
assert(result.image.all!(x => x == Img.black || x == Img.white));
|
||||
} body {
|
||||
/// True if inf <= x <= sup.
|
||||
static inInterval(T)(in T x, in T inf, in T sup) pure nothrow @safe @nogc {
|
||||
return x >= inf && x <= sup;
|
||||
}
|
||||
|
||||
/// Return 8-neighbours+1 of point (x,y) of given image, in order.
|
||||
static void neighbours(in Img I, in size_t x, in size_t y,
|
||||
out Neighbours n) pure nothrow @safe @nogc {
|
||||
n = [I[x,y-1], I[x+1,y-1], I[x+1,y], I[x+1,y+1], // P2,P3,P4,P5
|
||||
I[x,y+1], I[x-1,y+1], I[x-1,y], I[x-1,y-1], // P6,P7,P8,P9
|
||||
I[x,y-1]];
|
||||
}
|
||||
|
||||
if (image1.nx < 3 || image1.ny < 3) {
|
||||
image2.image[] = image1.image[];
|
||||
return image2;
|
||||
}
|
||||
|
||||
immutable static zeroOne = [0, 1]; //**
|
||||
Neighbours n;
|
||||
bool hasChanged;
|
||||
do {
|
||||
hasChanged = false;
|
||||
|
||||
foreach (immutable ab; TypeTuple!(tuple(2, 4), tuple(0, 6))) {
|
||||
foreach (immutable y; 1 .. image1.ny - 1) {
|
||||
foreach (immutable x; 1 .. image1.nx - 1) {
|
||||
neighbours(image1, x, y, n);
|
||||
if (image1[x, y] && // Cond. 0
|
||||
(!n[ab[0]] || !n[4] || !n[6]) && // Cond. 4
|
||||
(!n[0] || !n[2] || !n[ab[1]]) && // Cond. 3
|
||||
//n[].count([0, 1]) == 1 &&
|
||||
n[].count(zeroOne) == 1 && // Cond. 2
|
||||
// n[0 .. 8].sum in iota(2, 7)) {
|
||||
inInterval(n[0 .. 8].sum, 2, 6)) { // Cond. 1
|
||||
hasChanged = true;
|
||||
image2[x, y] = Img.black;
|
||||
} else
|
||||
image2[x, y] = image1[x, y];
|
||||
}
|
||||
}
|
||||
image1.swap(image2);
|
||||
}
|
||||
} while (hasChanged);
|
||||
|
||||
return image1;
|
||||
}
|
||||
|
||||
void main() {
|
||||
immutable before_txt = "
|
||||
##..###
|
||||
##..###
|
||||
##..###
|
||||
##..###
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
######.
|
||||
.......";
|
||||
|
||||
immutable small_rc = "
|
||||
................................
|
||||
.#########.......########.......
|
||||
.###...####.....####..####......
|
||||
.###....###.....###....###......
|
||||
.###...####.....###.............
|
||||
.#########......###.............
|
||||
.###.####.......###....###......
|
||||
.###..####..###.####..####.###..
|
||||
.###...####.###..########..###..
|
||||
................................";
|
||||
|
||||
immutable rc = "
|
||||
...........................................................
|
||||
.#################...................#############.........
|
||||
.##################...............################.........
|
||||
.###################............##################.........
|
||||
.########.....#######..........###################.........
|
||||
...######.....#######.........#######.......######.........
|
||||
...######.....#######........#######.......................
|
||||
...#################.........#######.......................
|
||||
...################..........#######.......................
|
||||
...#################.........#######.......................
|
||||
...######.....#######........#######.......................
|
||||
...######.....#######........#######.......................
|
||||
...######.....#######.........#######.......######.........
|
||||
.########.....#######..........###################.........
|
||||
.########.....#######.######....##################.######..
|
||||
.########.....#######.######......################.######..
|
||||
.########.....#######.######.........#############.######..
|
||||
...........................................................";
|
||||
|
||||
foreach (immutable txt; [before_txt, small_rc, rc]) {
|
||||
auto img = Img.fromText(txt);
|
||||
"From:".writeln;
|
||||
img.textualShow(/*bl=*/ '.', /*wh=*/ '#');
|
||||
"\nTo thinned:".writeln;
|
||||
img.zhangSuen(img.dup).textualShow(/*bl=*/ '.', /*wh=*/ '#');
|
||||
writeln;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var in = `
|
||||
00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000`
|
||||
|
||||
func main() {
|
||||
b := wbFromString(in, '1')
|
||||
b.zhangSuen()
|
||||
fmt.Println(b)
|
||||
}
|
||||
|
||||
const (
|
||||
white = 0
|
||||
black = 1
|
||||
)
|
||||
|
||||
type wbArray [][]byte // elements are white or black.
|
||||
|
||||
// parameter blk is character to read as black. otherwise kinda rigid,
|
||||
// expects ascii, leading newline, no trailing newline,
|
||||
// takes color from low bit of character.
|
||||
func wbFromString(s string, blk byte) wbArray {
|
||||
lines := strings.Split(s, "\n")[1:]
|
||||
b := make(wbArray, len(lines))
|
||||
for i, sl := range lines {
|
||||
bl := make([]byte, len(sl))
|
||||
for j := 0; j < len(sl); j++ {
|
||||
bl[j] = sl[j] & 1
|
||||
}
|
||||
b[i] = bl
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// rigid again, hard coded to output space for white, # for black,
|
||||
// no leading or trailing newline.
|
||||
var sym = [2]byte{
|
||||
white: ' ',
|
||||
black: '#',
|
||||
}
|
||||
|
||||
func (b wbArray) String() string {
|
||||
b2 := bytes.Join(b, []byte{'\n'})
|
||||
for i, b1 := range b2 {
|
||||
if b1 > 1 {
|
||||
continue
|
||||
}
|
||||
b2[i] = sym[b1]
|
||||
}
|
||||
return string(b2)
|
||||
}
|
||||
|
||||
// neighbor offsets
|
||||
var nb = [...][2]int{
|
||||
2: {-1, 0}, // p2 offsets
|
||||
3: {-1, 1}, // ...
|
||||
4: {0, 1},
|
||||
5: {1, 1},
|
||||
6: {1, 0},
|
||||
7: {1, -1},
|
||||
8: {0, -1},
|
||||
9: {-1, -1}, // p9 offsets
|
||||
}
|
||||
|
||||
func (b wbArray) reset(en []int) (rs bool) {
|
||||
var r, c int
|
||||
var p [10]byte
|
||||
|
||||
readP := func() {
|
||||
for nx := 1; nx <= 9; nx++ {
|
||||
n := nb[nx]
|
||||
p[nx] = b[r+n[0]][c+n[1]]
|
||||
}
|
||||
}
|
||||
|
||||
shiftRead := func() {
|
||||
n := nb[3]
|
||||
p[9], p[2], p[3] = p[2], p[3], b[r+n[0]][c+n[1]]
|
||||
n = nb[4]
|
||||
p[8], p[1], p[4] = p[1], p[4], b[r+n[0]][c+n[1]]
|
||||
n = nb[5]
|
||||
p[7], p[6], p[5] = p[6], p[5], b[r+n[0]][c+n[1]]
|
||||
}
|
||||
|
||||
// returns "A", count of white->black transitions in circuit of neighbors
|
||||
// of an interior pixel b[r][c]
|
||||
countA := func() (ct byte) {
|
||||
bit := p[9]
|
||||
for nx := 2; nx <= 9; nx++ {
|
||||
last := bit
|
||||
bit = p[nx]
|
||||
if last == white {
|
||||
ct += bit
|
||||
}
|
||||
}
|
||||
return ct
|
||||
}
|
||||
|
||||
// returns "B", count of black pixels neighboring interior pixel b[r][c].
|
||||
countB := func() (ct byte) {
|
||||
for nx := 2; nx <= 9; nx++ {
|
||||
ct += p[nx]
|
||||
}
|
||||
return ct
|
||||
}
|
||||
|
||||
lastRow := len(b) - 1
|
||||
lastCol := len(b[0]) - 1
|
||||
|
||||
mark := make([][]bool, lastRow)
|
||||
for r = range mark {
|
||||
mark[r] = make([]bool, lastCol)
|
||||
}
|
||||
|
||||
for r = 1; r < lastRow; r++ {
|
||||
c = 1
|
||||
readP()
|
||||
for { // column loop
|
||||
m := false
|
||||
// test for failure of any of the five conditions,
|
||||
if !(p[1] == black) {
|
||||
goto markDone
|
||||
}
|
||||
if b1 := countB(); !(2 <= b1 && b1 <= 6) {
|
||||
goto markDone
|
||||
}
|
||||
if !(countA() == 1) {
|
||||
goto markDone
|
||||
}
|
||||
{
|
||||
e1, e2 := p[en[1]], p[en[2]]
|
||||
if !(p[en[0]]&e1&e2 == 0) {
|
||||
goto markDone
|
||||
}
|
||||
if !(e1&e2&p[en[3]] == 0) {
|
||||
goto markDone
|
||||
}
|
||||
}
|
||||
// no conditions failed, mark this pixel for reset
|
||||
m = true
|
||||
rs = true // and mark that image changes
|
||||
markDone:
|
||||
mark[r][c] = m
|
||||
c++
|
||||
if c == lastCol {
|
||||
break
|
||||
}
|
||||
shiftRead()
|
||||
}
|
||||
}
|
||||
if rs {
|
||||
for r = 1; r < lastRow; r++ {
|
||||
for c = 1; c < lastCol; c++ {
|
||||
if mark[r][c] {
|
||||
b[r][c] = white
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
var step1 = []int{2, 4, 6, 8}
|
||||
var step2 = []int{4, 2, 8, 6}
|
||||
|
||||
func (b wbArray) zhangSuen() {
|
||||
for {
|
||||
rs1 := b.reset(step1)
|
||||
rs2 := b.reset(step2)
|
||||
if !rs1 && !rs2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
def zhangSuen(text) {
|
||||
def image = text.split('\n').collect { line -> line.collect { it == '#' ? 1 : 0} }
|
||||
def p2, p3, p4, p5, p6, p7, p8, p9
|
||||
def step1 = { (p2 * p4 * p6 == 0) && (p4 * p6 * p8 == 0) }
|
||||
def step2 = { (p2 * p4 * p8 == 0) && (p2 * p6 * p8 == 0) }
|
||||
def reduce = { step ->
|
||||
def toWhite = []
|
||||
image.eachWithIndex{ line, y ->
|
||||
line.eachWithIndex{ pixel, x ->
|
||||
if (!pixel) return
|
||||
(p2, p3, p4, p5, p6, p7, p8, p9) = [image[y-1][x], image[y-1][x+1], image[y][x+1], image[y+1][x+1], image[y+1][x], image[y+1][x-1], image[y][x-1], image[y-1][x-1]]
|
||||
def a = [[p2,p3],[p3,p4],[p4,p5],[p5,p6],[p6,p7],[p7,p8],[p8,p9],[p9,p2]].collect { a1, a2 -> (a1 == 0 && a2 ==1) ? 1 : 0 }.sum()
|
||||
def b = [p2, p3, p4, p5, p6, p7, p8, p9].sum()
|
||||
if (a != 1 || b < 2 || b > 6) return
|
||||
|
||||
if (step.call()) toWhite << [y,x]
|
||||
}
|
||||
}
|
||||
toWhite.each { y, x -> image[y][x] = 0 }
|
||||
!toWhite.isEmpty()
|
||||
}
|
||||
|
||||
while (reduce(step1) | reduce(step2));
|
||||
image.collect { line -> line.collect { it ? '#' : '.' }.join('') }.join('\n')
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
def small = """\
|
||||
................................
|
||||
.#########.......########.......
|
||||
.###...####.....####..####......
|
||||
.###....###.....###....###......
|
||||
.###...####.....###.............
|
||||
.#########......###.............
|
||||
.###.####.......###....###......
|
||||
.###..####..###.####..####.###..
|
||||
.###...####.###..########..###..
|
||||
................................""".stripIndent()
|
||||
|
||||
def large = """\
|
||||
...........................................................
|
||||
.#################...................#############.........
|
||||
.##################...............################.........
|
||||
.###################............##################.........
|
||||
.########.....#######..........###################.........
|
||||
...######.....#######.........#######.......######.........
|
||||
...######.....#######........#######.......................
|
||||
...#################.........#######.......................
|
||||
...################..........#######.......................
|
||||
...#################.........#######.......................
|
||||
...######.....#######........#######.......................
|
||||
...######.....#######........#######.......................
|
||||
...######.....#######.........#######.......######.........
|
||||
.########.....#######..........###################.........
|
||||
.########.....#######.######....##################.######..
|
||||
.########.....#######.######......################.######..
|
||||
.########.....#######.######.........#############.######..
|
||||
...........................................................""".stripIndent()
|
||||
|
||||
[small, large].each {
|
||||
println "From:"
|
||||
println it
|
||||
println "To:"
|
||||
println zhangSuen(it)
|
||||
println()
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
import Data.Array
|
||||
import qualified Data.List as List
|
||||
|
||||
data BW = Black | White
|
||||
deriving (Eq, Show)
|
||||
|
||||
type Index = (Int, Int)
|
||||
type BWArray = Array Index BW
|
||||
|
||||
toBW :: Char -> BW
|
||||
toBW '0' = White
|
||||
toBW '1' = Black
|
||||
toBW ' ' = White
|
||||
toBW '#' = Black
|
||||
toBW _ = error "toBW: illegal char"
|
||||
|
||||
toBWArray :: [String] -> BWArray
|
||||
toBWArray strings = arr
|
||||
where
|
||||
height = length strings
|
||||
width = minimum $ map length strings
|
||||
arr = listArray ((0, 0), (width - 1, height - 1))
|
||||
. map toBW . concat . List.transpose $ map (take width) strings
|
||||
|
||||
toChar :: BW -> Char
|
||||
toChar White = ' '
|
||||
toChar Black = '#'
|
||||
|
||||
chunksOf :: Int -> [a] -> [[a]]
|
||||
chunksOf _ [] = []
|
||||
chunksOf n xs = take n xs : (chunksOf n $ drop n xs)
|
||||
|
||||
showBWArray :: BWArray -> String
|
||||
showBWArray arr =
|
||||
List.intercalate "\n" . List.transpose
|
||||
. chunksOf (height + 1) . map toChar $ elems arr
|
||||
where
|
||||
(_, (_, height)) = bounds arr
|
||||
|
||||
add :: Num a => (a, a) -> (a, a) -> (a, a)
|
||||
add (a, b) (x, y) = (a + x, b + y)
|
||||
|
||||
within :: Ord a => ((a, a), (a, a)) -> (a, a) -> Bool
|
||||
within ((a, b), (c, d)) (x, y) =
|
||||
a <= x && x <= c &&
|
||||
b <= y && y <= d
|
||||
|
||||
p2, p3, p4, p5, p6, p7, p8, p9 :: Index
|
||||
p2 = ( 0, -1)
|
||||
p3 = ( 1, -1)
|
||||
p4 = ( 1, 0)
|
||||
p5 = ( 1, 1)
|
||||
p6 = ( 0, 1)
|
||||
p7 = (-1, 1)
|
||||
p8 = (-1, 0)
|
||||
p9 = (-1, -1)
|
||||
|
||||
ixamap :: Ix i => ((i, a) -> b) -> Array i a -> Array i b
|
||||
ixamap f a = listArray (bounds a) $ map f $ assocs a
|
||||
|
||||
thin :: BWArray -> BWArray
|
||||
thin arr =
|
||||
if pass2 == arr then pass2 else thin pass2
|
||||
where
|
||||
(low, high) = bounds arr
|
||||
lowB = low `add` (1, 1)
|
||||
highB = high `add` (-1, -1)
|
||||
isInner = within (lowB, highB)
|
||||
offs p = map (add p) [p2, p3, p4, p5, p6, p7, p8, p9]
|
||||
trans c (a, b) = if a == White && b == Black then c + 1 else c
|
||||
zipshift xs = zip xs (drop 1 xs ++ xs)
|
||||
transitions a = (== (1 :: Int)) . foldl trans 0 . zipshift . map (a !) . offs
|
||||
within2to6 n = 2 <= n && n <= 6
|
||||
blacks a p = within2to6 . length . filter ((== Black) . (a !)) $ offs p
|
||||
oneWhite xs a p = any ((== White) . (a !) . add p) xs
|
||||
oneRight = oneWhite [p2, p4, p6]
|
||||
oneDown = oneWhite [p4, p6, p8]
|
||||
oneUp = oneWhite [p2, p4, p8]
|
||||
oneLeft = oneWhite [p2, p6, p8]
|
||||
precond a p = (a ! p == Black) && isInner p && blacks a p && transitions a p
|
||||
stage1 a p = precond a p && oneRight a p && oneDown a p
|
||||
stage2 a p = precond a p && oneUp a p && oneLeft a p
|
||||
stager f (p, d) = if f p then White else d
|
||||
pass1 = ixamap (stager $ stage1 arr) arr
|
||||
pass2 = ixamap (stager $ stage2 pass1) pass1
|
||||
|
||||
sampleExA :: [String]
|
||||
sampleExA =
|
||||
["00000000000000000000000000000000"
|
||||
,"01111111110000000111111110000000"
|
||||
,"01110001111000001111001111000000"
|
||||
,"01110000111000001110000111000000"
|
||||
,"01110001111000001110000000000000"
|
||||
,"01111111110000001110000000000000"
|
||||
,"01110111100000001110000111000000"
|
||||
,"01110011110011101111001111011100"
|
||||
,"01110001111011100111111110011100"
|
||||
,"00000000000000000000000000000000"]
|
||||
|
||||
sampleExB :: [String]
|
||||
sampleExB =
|
||||
[" "
|
||||
," ################# ############# "
|
||||
," ################## ################ "
|
||||
," ################### ################## "
|
||||
," ######## ####### ################### "
|
||||
," ###### ####### ####### ###### "
|
||||
," ###### ####### ####### "
|
||||
," ################# ####### "
|
||||
," ################ ####### "
|
||||
," ################# ####### "
|
||||
," ###### ####### ####### "
|
||||
," ###### ####### ####### "
|
||||
," ###### ####### ####### ###### "
|
||||
," ######## ####### ################### "
|
||||
," ######## ####### ###### ################## ###### "
|
||||
," ######## ####### ###### ################ ###### "
|
||||
," ######## ####### ###### ############# ###### "
|
||||
," "]
|
||||
|
||||
main :: IO ()
|
||||
main = mapM_ (putStrLn . showBWArray . thin . toBWArray) [sampleExA, sampleExB]
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
isBlackPx=: '1'&=;._2 NB. boolean array of black pixels
|
||||
toImage=: [: , LF ,.~ '01' {~ ] NB. convert to original representation
|
||||
frameImg=: 0 ,. 0 , >:@$ {. ] NB. adds border of 0's to image
|
||||
|
||||
neighbrs=: adverb define NB. applies verb u to neighbourhoods
|
||||
(1 1 ,: 3 3) u;._3 y
|
||||
)
|
||||
|
||||
Bdry=: 1 2 5 8 7 6 3 0 1 NB. map pixel index to neighbour order
|
||||
getPx=: { , NB. get desired pixels from neighbourhood
|
||||
Ap1=: [: +/ 2 </\ Bdry&getPx NB. count 0->1 transitions
|
||||
Bp1=: [: +/ [: }. Bdry&getPx NB. count black neighbours
|
||||
|
||||
c11=: (2&<: *. <:&6)@Bp1 NB. step 1, condition 1
|
||||
c12=: 1 = Ap1 NB. ...
|
||||
c13=: 0 e. 1 5 7&getPx
|
||||
c14=: 0 e. 5 7 3&getPx
|
||||
c23=: 0 e. 1 5 3&getPx NB. step2, condition 3
|
||||
c24=: 0 e. 1 7 3&getPx
|
||||
|
||||
cond1=: c11 *. c12 *. c13 *. c14 NB. step1 conditions
|
||||
cond2=: c11 *. c12 *. c23 *. c24 NB. step2 conditions
|
||||
whiten=: [ * -.@:*. NB. make black pixels white
|
||||
step1=: whiten frameImg@(cond1 neighbrs)
|
||||
step2=: whiten frameImg@(cond2 neighbrs)
|
||||
|
||||
zhangSuen=: [: toImage [: step2@step1^:_ isBlackPx
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
zhangSuenX=: verb define
|
||||
img=. isBlackPx y
|
||||
whilst. 0 < +/ , msk1 +.&-. msk2 do.
|
||||
msk1=. (-.@:*. [: frameImg cond1 neighbrs) img
|
||||
img=. msk1 * img
|
||||
msk2=. (-.@:*. [: frameImg cond2 neighbrs) img
|
||||
img=. msk2 * img
|
||||
end.
|
||||
toImage img
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
toASCII=: ' #' {~ '1'&=;._2 NB. convert to ASCII representation
|
||||
|
||||
ExampleImg=: noun define
|
||||
00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000
|
||||
)
|
||||
|
||||
toASCII zhangSuen ExampleImg
|
||||
|
||||
####### ######
|
||||
# # ##
|
||||
# # #
|
||||
# # #
|
||||
##### # #
|
||||
## #
|
||||
# # ## ## #
|
||||
# ####
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import java.awt.Point;
|
||||
import java.util.*;
|
||||
|
||||
public class ZhangSuen {
|
||||
|
||||
final static String[] image = {
|
||||
" ",
|
||||
" ################# ############# ",
|
||||
" ################## ################ ",
|
||||
" ################### ################## ",
|
||||
" ######## ####### ################### ",
|
||||
" ###### ####### ####### ###### ",
|
||||
" ###### ####### ####### ",
|
||||
" ################# ####### ",
|
||||
" ################ ####### ",
|
||||
" ################# ####### ",
|
||||
" ###### ####### ####### ",
|
||||
" ###### ####### ####### ",
|
||||
" ###### ####### ####### ###### ",
|
||||
" ######## ####### ################### ",
|
||||
" ######## ####### ###### ################## ###### ",
|
||||
" ######## ####### ###### ################ ###### ",
|
||||
" ######## ####### ###### ############# ###### ",
|
||||
" "};
|
||||
|
||||
final static int[][] nbrs = {{0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1},
|
||||
{-1, 1}, {-1, 0}, {-1, -1}, {0, -1}};
|
||||
|
||||
final static int[][][] nbrGroups = {{{0, 2, 4}, {2, 4, 6}}, {{0, 2, 6},
|
||||
{0, 4, 6}}};
|
||||
|
||||
static List<Point> toWhite = new ArrayList<>();
|
||||
static char[][] grid;
|
||||
|
||||
public static void main(String[] args) {
|
||||
grid = new char[image.length][];
|
||||
for (int r = 0; r < image.length; r++)
|
||||
grid[r] = image[r].toCharArray();
|
||||
|
||||
thinImage();
|
||||
}
|
||||
|
||||
static void thinImage() {
|
||||
boolean firstStep = false;
|
||||
boolean hasChanged;
|
||||
|
||||
do {
|
||||
hasChanged = false;
|
||||
firstStep = !firstStep;
|
||||
|
||||
for (int r = 1; r < grid.length - 1; r++) {
|
||||
for (int c = 1; c < grid[0].length - 1; c++) {
|
||||
|
||||
if (grid[r][c] != '#')
|
||||
continue;
|
||||
|
||||
int nn = numNeighbors(r, c);
|
||||
if (nn < 2 || nn > 6)
|
||||
continue;
|
||||
|
||||
if (numTransitions(r, c) != 1)
|
||||
continue;
|
||||
|
||||
if (!atLeastOneIsWhite(r, c, firstStep ? 0 : 1))
|
||||
continue;
|
||||
|
||||
toWhite.add(new Point(c, r));
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (Point p : toWhite)
|
||||
grid[p.y][p.x] = ' ';
|
||||
toWhite.clear();
|
||||
|
||||
} while (hasChanged || firstStep);
|
||||
|
||||
printResult();
|
||||
}
|
||||
|
||||
static int numNeighbors(int r, int c) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < nbrs.length - 1; i++)
|
||||
if (grid[r + nbrs[i][1]][c + nbrs[i][0]] == '#')
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
static int numTransitions(int r, int c) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < nbrs.length - 1; i++)
|
||||
if (grid[r + nbrs[i][1]][c + nbrs[i][0]] == ' ') {
|
||||
if (grid[r + nbrs[i + 1][1]][c + nbrs[i + 1][0]] == '#')
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static boolean atLeastOneIsWhite(int r, int c, int step) {
|
||||
int count = 0;
|
||||
int[][] group = nbrGroups[step];
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (int j = 0; j < group[i].length; j++) {
|
||||
int[] nbr = nbrs[group[i][j]];
|
||||
if (grid[r + nbr[1]][c + nbr[0]] == ' ') {
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count > 1;
|
||||
}
|
||||
|
||||
static void printResult() {
|
||||
for (char[] row : grid)
|
||||
System.out.println(row);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
nB[mat_] := Delete[mat // Flatten, 5] // Total;
|
||||
|
||||
nA[mat_] := Module[{l},
|
||||
l = Flatten[mat][[{2, 3, 6, 9, 8, 7, 4, 1, 2}]];
|
||||
Total[Map[If[#[[1]] == 0 && #[[2]] == 1, 1, 0] &,
|
||||
Partition[l, 2, 1]]]
|
||||
];
|
||||
|
||||
iW1[mat_] :=
|
||||
Module[{l = Flatten[mat]},
|
||||
If[Apply[Times, l[[{2, 6, 8}]]] + Apply[Times, l[[{4, 6, 8}]]] ==
|
||||
0, 0, 1]];
|
||||
iW2[mat_] :=
|
||||
Module[{l = Flatten[mat]},
|
||||
If[Apply[Times, l[[{2, 6, 4}]]] + Apply[Times, l[[{4, 2, 8}]]] ==
|
||||
0, 0, 1]];
|
||||
|
||||
check[i_, j_, dat_, t_] := Module[{mat, d = Dimensions[dat], r, c},
|
||||
r = d[[1]];
|
||||
c = d[[2]];
|
||||
If[i > 1 && i < r && j > 1 && j < c,
|
||||
mat = dat[[i - 1 ;; i + 1, j - 1 ;; j + 1]];
|
||||
If[dat[[i, j]] == 1 && nA[mat] == 1 && 2 <= nB[mat] <= 6 &&
|
||||
If[t == 1, iW1[mat], iW2[mat]] == 0, 0, dat[[i, j]]],
|
||||
dat[[i, j]]
|
||||
]];
|
||||
|
||||
iter[dat_] :=
|
||||
Module[{i =
|
||||
Flatten[Outer[List, Range[Dimensions[dat][[1]]],
|
||||
Range[Dimensions[dat][[2]]]], 1], tmp},
|
||||
tmp = Partition[check[#[[1]], #[[2]], dat, 1] & /@ i,
|
||||
Dimensions[dat][[2]]];
|
||||
Partition[check[#[[1]], #[[2]], tmp, 2] & /@ i,
|
||||
Dimensions[tmp][[2]]]];
|
||||
|
||||
|
||||
FixedPoint[iter, dat]
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
zhang: procedure options (main); /* 8 July 2014 */
|
||||
|
||||
declare pic(10) bit(32) initial (
|
||||
'00000000000000000000000000000000'b,
|
||||
'01111111110000000111111110000000'b,
|
||||
'01110001111000001111001111000000'b,
|
||||
'01110000111000001110000111000000'b,
|
||||
'01110001111000001110000000000000'b,
|
||||
'01111111110000001110000000000000'b,
|
||||
'01110111100000001110000111000000'b,
|
||||
'01110011110011101111001111011100'b,
|
||||
'01110001111011100111111110011100'b,
|
||||
'00000000000000000000000000000000'b );
|
||||
declare image (10,32) bit(1) defined pic;
|
||||
declare status (10,32) fixed decimal (1);
|
||||
declare changes bit(1);
|
||||
declare (i, j, k, m, n) fixed binary;
|
||||
|
||||
m = hbound(image,1); n = hbound(image,2);
|
||||
|
||||
call display;
|
||||
|
||||
/* Pixel labelling for pixels surrounding P1, co-ordinates (i,j). */
|
||||
/* P9 P2 P3 */
|
||||
/* P8 P1 P4 */
|
||||
/* P7 P6 P5 */
|
||||
|
||||
do k = 1 to 10 until (^changes);
|
||||
changes = '0'b;
|
||||
/* Set conditions as follows: */
|
||||
/* (0) The pixel is black and has eight neighbours */
|
||||
/* (1) 2 < = B(P1) < = 6 */
|
||||
/* (2) A(P1) = 1 */
|
||||
/* (3) At least one of P2 and P4 and P6 is white */
|
||||
/* (4) At least one of P4 and P6 and P8 is white */
|
||||
status = -1;
|
||||
do i = 2 to m-1;
|
||||
do j = 2 to n-1;
|
||||
if image(i,j) then
|
||||
if B(i,j) >= 2 & B(i,j) <= 6 then
|
||||
if A(i,j) = 1 then
|
||||
if ^image(i-1,j) | ^image(i,j+1) | ^image(i+1,j) then
|
||||
if ^image(i,j+1) | ^image(i+1,j) | ^image(i,j-1) then
|
||||
status(i,j) = 4;
|
||||
end;
|
||||
end;
|
||||
/* Having determined a status for every bit in the image, */
|
||||
/* change those bits to white. */
|
||||
do i = 2 to m-1;
|
||||
do j = 2 to n-1;
|
||||
if status(i,j) ^= -1 then do; image(i,j) = '0'b; changes = '1'b; end;
|
||||
end;
|
||||
end;
|
||||
|
||||
/* Set conditions as follows: */
|
||||
/* (0) The pixel is black and has eight neighbours */
|
||||
/* (1) 2 < = B(P1) < = 6 */
|
||||
/* (2) A(P1) = 1 */
|
||||
/* (3) At least one of P2 and P4 and P8 is white */
|
||||
/* (4) At least one of P2 and P6 and P8 is white */
|
||||
status = -1;
|
||||
do i = 2 to m-1;
|
||||
do j = 2 to n-1;
|
||||
if image(i,j) then
|
||||
if B(i,j) >= 2 & B(i,j) <= 6 then
|
||||
if A(i,j) = 1 then
|
||||
if ^image(i-1,j) | ^image(i,j+1) | ^image(i,j-1) then
|
||||
if ^image(i-1,j) | ^image(i+1,j) | ^image(i,j-1) then
|
||||
status(i,j) = 4;
|
||||
end;
|
||||
end;
|
||||
/* Having determined a status for every bit in the image, */
|
||||
/* change those bits to white. */
|
||||
do i = 2 to m-1;
|
||||
do j = 2 to n-1;
|
||||
if status(i,j) ^= -1 then do; image(i,j) = '0'b; changes = '1'b; end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end; /* of the "until" loop */
|
||||
|
||||
put skip list ('Final image after ' || trim(k) || ' iterations:');
|
||||
call display;
|
||||
|
||||
display: procedure;
|
||||
declare (i, j) fixed binary;
|
||||
declare c character (1);
|
||||
|
||||
do i = 1 to m;
|
||||
put skip edit ('row:', i) (A, F(3));
|
||||
do j = 1 to n;
|
||||
if image(i,j) then c = '.'; else c = ' ';
|
||||
put edit (c) (A);
|
||||
end;
|
||||
end;
|
||||
put skip;
|
||||
end;
|
||||
|
||||
/* Returns the number of transitions from white to black from P2 through P9 and P2. */
|
||||
A: procedure (i,j) returns (fixed binary);
|
||||
declare (i,j) fixed binary nonassignable;
|
||||
declare n(2:10) bit(1);
|
||||
|
||||
n(2) = image(i-1,j); n(3) = image(i-1,j+1);
|
||||
n(4) = image(i, j+1); n(5) = image(i+1,j+1);
|
||||
n(6) = image(i+1,j); n(7) = image(i+1,j-1);
|
||||
n(8) = image(i,j-1); n(9) = image(i-1,j-1);
|
||||
n(10) = image(i-1,j);
|
||||
|
||||
return ( tally(string(n), '01'b) );
|
||||
end A;
|
||||
|
||||
/* Count the pixel neighbors of P1 that are black */
|
||||
B: procedure (i, j) returns (fixed binary);
|
||||
declare (i,j) fixed binary nonassignable;
|
||||
declare s fixed binary;
|
||||
|
||||
s = image(i-1,j-1) + image(i-1,j) + image(i-1,j+1);
|
||||
s = s + image(i,j-1) + image(i,j+1);
|
||||
return ( s + image(i+1,j-1) + image(i+1,j) + image(i+1,j+1) );
|
||||
end B;
|
||||
|
||||
end zhang;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
constant DEBUG = 1;
|
||||
|
||||
my @lines = ([.ords X+& 1] for lines); # The low bits Just Work.
|
||||
my \v = +@lines;
|
||||
my \h = +@lines[0];
|
||||
my @black = @lines.map: *.values; # Flatten to 1-dimensional.
|
||||
|
||||
my \p8 = [-h-1, -h+0, -h+1, # Flatland distances to 8 neighbors.
|
||||
0-1, 0+1,
|
||||
h-1, h+0, h+1].[1,2,4,7,6,5,3,0]; # (in cycle order)
|
||||
|
||||
# Candidates have 8 neighbors and are known black
|
||||
my @cand = grep { @black[$_] }, do
|
||||
for 1..v-2 X 1..h-2 -> \y,\x { y*h + x }
|
||||
|
||||
repeat while my @goners1 or my @goners2 {
|
||||
sub seewhite (\w1,\w2) {
|
||||
sub cycles (@neighbors) { [+] @neighbors Z< @neighbors[].rotate }
|
||||
sub blacks (@neighbors) { [+] @neighbors }
|
||||
|
||||
my @prior = @cand; @cand = ();
|
||||
|
||||
gather for @prior -> \p {
|
||||
my \n = @black[p8 X+ p];
|
||||
if cycles(n) == 1 and 2 <= blacks(n) <= 6 and n[w1].any == 0 and n[w2].any == 0
|
||||
{ take p }
|
||||
else { @cand.push: p }
|
||||
}
|
||||
}
|
||||
|
||||
@goners1 = seewhite (0,2,4), (2,4,6);
|
||||
@black[@goners1] = 0 xx *;
|
||||
say "Ping: {[+] @black} remaining after removing ", @goners1 if DEBUG;
|
||||
|
||||
@goners2 = seewhite (0,2,6), (0,4,6);
|
||||
@black[@goners2] = 0 xx *;
|
||||
say "Pong: {[+] @black} remaining after removing ", @goners2 if DEBUG;
|
||||
}
|
||||
|
||||
say @black.splice(0,h).join.trans('01' => '.#') while @black;
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Example from [http://nayefreza.wordpress.com/2013/05/11/zhang-suen-thinning-algorithm-java-implementation/ this blog post].
|
||||
beforeTxt = '''\
|
||||
1100111
|
||||
1100111
|
||||
1100111
|
||||
1100111
|
||||
1100110
|
||||
1100110
|
||||
1100110
|
||||
1100110
|
||||
1100110
|
||||
1100110
|
||||
1100110
|
||||
1100110
|
||||
1111110
|
||||
0000000\
|
||||
'''
|
||||
|
||||
# Thanks to [http://www.network-science.de/ascii/ this site] and vim for these next two examples
|
||||
smallrc01 = '''\
|
||||
00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000\
|
||||
'''
|
||||
|
||||
rc01 = '''\
|
||||
00000000000000000000000000000000000000000000000000000000000
|
||||
01111111111111111100000000000000000001111111111111000000000
|
||||
01111111111111111110000000000000001111111111111111000000000
|
||||
01111111111111111111000000000000111111111111111111000000000
|
||||
01111111100000111111100000000001111111111111111111000000000
|
||||
00011111100000111111100000000011111110000000111111000000000
|
||||
00011111100000111111100000000111111100000000000000000000000
|
||||
00011111111111111111000000000111111100000000000000000000000
|
||||
00011111111111111110000000000111111100000000000000000000000
|
||||
00011111111111111111000000000111111100000000000000000000000
|
||||
00011111100000111111100000000111111100000000000000000000000
|
||||
00011111100000111111100000000111111100000000000000000000000
|
||||
00011111100000111111100000000011111110000000111111000000000
|
||||
01111111100000111111100000000001111111111111111111000000000
|
||||
01111111100000111111101111110000111111111111111111011111100
|
||||
01111111100000111111101111110000001111111111111111011111100
|
||||
01111111100000111111101111110000000001111111111111011111100
|
||||
00000000000000000000000000000000000000000000000000000000000\
|
||||
'''
|
||||
|
||||
def intarray(binstring):
|
||||
'''Change a 2D matrix of 01 chars into a list of lists of ints'''
|
||||
return [[1 if ch == '1' else 0 for ch in line]
|
||||
for line in binstring.strip().split()]
|
||||
|
||||
def chararray(intmatrix):
|
||||
'''Change a 2d list of lists of 1/0 ints into lines of 1/0 chars'''
|
||||
return '\n'.join(''.join(str(p) for p in row) for row in intmatrix)
|
||||
|
||||
def toTxt(intmatrix):
|
||||
'''Change a 2d list of lists of 1/0 ints into lines of '#' and '.' chars'''
|
||||
return '\n'.join(''.join(('#' if p else '.') for p in row) for row in intmatrix)
|
||||
|
||||
def neighbours(x, y, image):
|
||||
'''Return 8-neighbours of point p1 of picture, in order'''
|
||||
i = image
|
||||
x1, y1, x_1, y_1 = x+1, y-1, x-1, y+1
|
||||
#print ((x,y))
|
||||
return [i[y1][x], i[y1][x1], i[y][x1], i[y_1][x1], # P2,P3,P4,P5
|
||||
i[y_1][x], i[y_1][x_1], i[y][x_1], i[y1][x_1]] # P6,P7,P8,P9
|
||||
|
||||
def transitions(neighbours):
|
||||
n = neighbours + neighbours[0:1] # P2, ... P9, P2
|
||||
return sum((n1, n2) == (0, 1) for n1, n2 in zip(n, n[1:]))
|
||||
|
||||
def zhangSuen(image):
|
||||
changing1 = changing2 = [(-1, -1)]
|
||||
while changing1 or changing2:
|
||||
# Step 1
|
||||
changing1 = []
|
||||
for y in range(1, len(image) - 1):
|
||||
for x in range(1, len(image[0]) - 1):
|
||||
P2,P3,P4,P5,P6,P7,P8,P9 = n = neighbours(x, y, image)
|
||||
if (image[y][x] == 1 and # (Condition 0)
|
||||
P4 * P6 * P8 == 0 and # Condition 4
|
||||
P2 * P4 * P6 == 0 and # Condition 3
|
||||
transitions(n) == 1 and # Condition 2
|
||||
2 <= sum(n) <= 6): # Condition 1
|
||||
changing1.append((x,y))
|
||||
for x, y in changing1: image[y][x] = 0
|
||||
# Step 2
|
||||
changing2 = []
|
||||
for y in range(1, len(image) - 1):
|
||||
for x in range(1, len(image[0]) - 1):
|
||||
P2,P3,P4,P5,P6,P7,P8,P9 = n = neighbours(x, y, image)
|
||||
if (image[y][x] == 1 and # (Condition 0)
|
||||
P2 * P6 * P8 == 0 and # Condition 4
|
||||
P2 * P4 * P8 == 0 and # Condition 3
|
||||
transitions(n) == 1 and # Condition 2
|
||||
2 <= sum(n) <= 6): # Condition 1
|
||||
changing2.append((x,y))
|
||||
for x, y in changing2: image[y][x] = 0
|
||||
#print changing1
|
||||
#print changing2
|
||||
return image
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for picture in (beforeTxt, smallrc01, rc01):
|
||||
image = intarray(picture)
|
||||
print('\nFrom:\n%s' % toTxt(image))
|
||||
after = zhangSuen(image)
|
||||
print('\nTo thinned:\n%s' % toTxt(after))
|
||||
1
Task/Zhang-Suen-thinning-algorithm/README
Normal file
1
Task/Zhang-Suen-thinning-algorithm/README
Normal file
|
|
@ -0,0 +1 @@
|
|||
Data source: http://rosettacode.org/wiki/Zhang-Suen_thinning_algorithm
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*REXX pgm thins a NxM char grid using the Zhang-Suen thinning algorithm*/
|
||||
parse arg iFID .; if iFID=='' then iFID='ZHANG_SUEN.DAT'
|
||||
white=' '; @.=white /* [↓] read the input char grid. */
|
||||
do row=1 while lines(iFID)\==0; _=linein(iFID)
|
||||
_=translate(_,,.0); cols.row=length(_)
|
||||
do col=1 for cols.row; @.row.col=substr(_,col,1)
|
||||
end /*col*/ /* [↑] assign whole row of chars*/
|
||||
end /*row*/
|
||||
rows=row-1 /* adjust ROWS because of DO loop*/
|
||||
call show@ 'input file ' iFID " contents:" /*show the input char grid.*/
|
||||
|
||||
do until changed==0; changed=0 /*keep slimming until we're done.*/
|
||||
do step=1 for 2 /*keep track of step 1 │ step 2.*/
|
||||
do r=1 for rows /*process all rows and columns. */
|
||||
do c=1 for cols.r; !.r.c=@.r.c /*assign alternate grid.*/
|
||||
if r==1|r==rows|c==1|c==cols.r then iterate /*is an edge?*/
|
||||
if @.r.c==white then iterate /*White? Then skip it.*/
|
||||
call Ps; b=b() /*define Ps and "b". */
|
||||
if b<2 | b>6 then iterate /*is B within range?*/
|
||||
if a()\==1 then iterate /*count the transitions.*/ /* ╔══╦══╦══╗ */
|
||||
if step==1 then if (p2 & p4 & p6) | p4 & p6 & p8 then iterate /* ║p9║p2║p3║ */
|
||||
if step==2 then if (p2 & p4 & p8) | p2 & p6 & p8 then iterate /* ╠══╬══╬══╣ */
|
||||
!.r.c=white /*set a grid character to white.*/ /* ║p8║p1║p4║ */
|
||||
changed=1 /*indicate a char was changed. */ /* ╠══╬══╬══╣ */
|
||||
end /*c*/ /* ║p7║p6║p5║ */
|
||||
end /*r*/ /* ╚══╩══╩══╝ */
|
||||
call copy!2@ /*copy alternate to working grid.*/
|
||||
end /*step*/
|
||||
end /*until changed==0*/
|
||||
|
||||
call show@ 'slimmed output:' /*display the slimmed char grid. */
|
||||
exit /*stick a fork in it, we're done.*/
|
||||
/*──────────────────────────────────subroutines─────────────────────────*/
|
||||
a: return (\p2==p3&p3)+(\p3==p4&p4)+(\p4==p5&p5)+(\p5==p6&p6)+(\p6==p7&p7)+(\p7==p8&p8)+(\p8==p9&p9)+(\p9==p2&p2)
|
||||
b: return p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9
|
||||
copy!2@: do r=1 for rows; do c=1 for cols.r; @.r.c=!.r.c; end;end; return
|
||||
show@: say; say arg(1); say; do r=1 for rows; _=; do c=1 for cols.r; _=_||@.r.c; end; say _; end; return
|
||||
|
||||
Ps: rm=r-1; rp=r+1; cm=c-1; cp=c+1 /*calculate shortcuts.*/
|
||||
p2=@.rm.c\==white; p3=@.rm.cp\==white; p4=@.r.cp\==white; p5=@.rp.cp\==white
|
||||
p6=@.rp.c\==white; p7=@.rp.cm\==white; p8=@.r.cm\==white; p9=@.rm.cm\==white; return
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
#lang racket
|
||||
(define (img-01string->vector str)
|
||||
(define lines (regexp-split "\n" str))
|
||||
(define h (length lines))
|
||||
(define w (if (zero? h) 0 (string-length (car lines))))
|
||||
(define v (for*/vector #:length (* w h)
|
||||
((l (in-list lines)) (p (in-string l)))
|
||||
(match p (#\0 0) (#\1 1) (#\# 1) (#\. 0))))
|
||||
(values v h w))
|
||||
|
||||
; Task (2) asks for "or an ASCII-art image of space/non-space characters."
|
||||
; Spaces don't really impress where the borders are, so we'll use a dot.
|
||||
(define cell->display-char (match-lambda (0 ".") (1 "#") (else "?")))
|
||||
|
||||
(define (display-img v w)
|
||||
(for ((p (in-vector v)) (col (in-naturals)))
|
||||
(printf "~a" (cell->display-char p))
|
||||
(when (= (modulo col w) (sub1 w)) (newline))))
|
||||
|
||||
; returns vector of ([P1's idx] P1 P2 ... P9)
|
||||
(define (Pns v w r c)
|
||||
(define i (+ c (* r w)))
|
||||
(define-syntax-rule (vi+ x) (vector-ref v (+ i x)))
|
||||
(define-syntax-rule (vi- x) (vector-ref v (- i x)))
|
||||
(vector i (vi+ 0) (vi- w) (vi+ (- 1 w))
|
||||
(vi+ 1) (vi+ (+ w 1)) (vi+ w)
|
||||
(vi+ (- w 1)) (vi- 1) (vi- (+ w 1))))
|
||||
|
||||
; Second argument to in-vector is the start offset;
|
||||
; We skip offset 0 (idx) and 1 (P1)
|
||||
(define (B Ps) (for/sum ((Pn (in-vector Ps 2))) Pn))
|
||||
|
||||
(define (A Ps)
|
||||
(define P2 (vector-ref Ps 2))
|
||||
(define-values (rv _)
|
||||
(for/fold ((acc 0) (Pn-1 P2))
|
||||
((Pn (in-sequences (in-vector Ps 3) (in-value P2))))
|
||||
(values (+ acc (if (and (= 0 Pn-1) (= 1 Pn)) 1 0)) Pn)))
|
||||
rv)
|
||||
|
||||
(define-syntax-rule (not-all-black? Pa Pb Pc) (zero? (* Pa Pb Pc)))
|
||||
(define (z-s-thin v h w)
|
||||
; return idx when thin necessary, #f otherwise
|
||||
(define (thin? Ps n/bour-check-1 n/bour-check-2)
|
||||
(match-define (vector idx P1 P2 _ P4 _ P6 _ P8 _) Ps)
|
||||
(and (= P1 1) (<= 2 (B Ps) 6) (= (A Ps) 1)
|
||||
(n/bour-check-1 P2 P4 P6 P8)
|
||||
(n/bour-check-2 P2 P4 P6 P8)
|
||||
idx))
|
||||
|
||||
(define (has-white?-246 P2 P4 P6 P8) (not-all-black? P2 P4 P6))
|
||||
(define (has-white?-468 P2 P4 P6 P8) (not-all-black? P4 P6 P8))
|
||||
(define (has-white?-248 P2 P4 P6 P8) (not-all-black? P2 P4 P8))
|
||||
(define (has-white?-268 P2 P4 P6 P8) (not-all-black? P2 P6 P8))
|
||||
(define (step-n even-Pn-check-1 even-Pn-check-2)
|
||||
(for*/list ((r (in-range 1 (- h 1)))
|
||||
(c (in-range 1 (- w 1)))
|
||||
(idx (in-value (thin? (Pns v w r c)
|
||||
even-Pn-check-1
|
||||
even-Pn-check-2)))
|
||||
#:when idx) idx))
|
||||
|
||||
(define (step-1) (step-n has-white?-246 has-white?-468))
|
||||
(define (step-2) (step-n has-white?-248 has-white?-268))
|
||||
(define (inner-z-s-thin)
|
||||
(define changed-list-1 (step-1))
|
||||
(for ((idx (in-list changed-list-1))) (vector-set! v idx 0))
|
||||
(define changed-list-2 (step-2))
|
||||
(for ((idx (in-list changed-list-2))) (vector-set! v idx 0))
|
||||
(unless (and (null? changed-list-1) (null? changed-list-2)) (inner-z-s-thin)))
|
||||
(inner-z-s-thin))
|
||||
|
||||
(define (read-display-thin-display-image img-str)
|
||||
(define-values (v h w) (img-01string->vector img-str))
|
||||
(printf "Original image:~%") (display-img v w)
|
||||
(z-s-thin v h w)
|
||||
(printf "Thinned image:~%") (display-img v w))
|
||||
|
||||
(define e.g.-image #<<EOS
|
||||
00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000
|
||||
EOS
|
||||
)
|
||||
|
||||
(define e.g.-image/2 #<<EOS
|
||||
##..###
|
||||
##..###
|
||||
##..###
|
||||
##..###
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
##..##.
|
||||
######.
|
||||
.......
|
||||
EOS
|
||||
)
|
||||
|
||||
(module+ main
|
||||
; (read-display-thin-display-image e.g.-image/2)
|
||||
; (newline)
|
||||
(read-display-thin-display-image e.g.-image))
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
class ZhangSuen
|
||||
NEIGHBOUR8 = [[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1]] # 8 neighbors
|
||||
CIRCULARS = NEIGHBOUR8 + [NEIGHBOUR8.first] # P2, ... P9, P2
|
||||
def initialize(str, black="#")
|
||||
s1 = str.each_line.map{|line| line.chomp.each_char.map{|c| c==black ? 1 : 0}}
|
||||
s2 = s1.map{|line| line.map{0}}
|
||||
xrange = 1 ... s1.size-1
|
||||
yrange = 1 ... s1[0].size-1
|
||||
printout(s1)
|
||||
begin
|
||||
@r = 0
|
||||
xrange.each{|x| yrange.each{|y| s2[x][y] = s1[x][y] - zs(s1,x,y,1)}} # Step 1
|
||||
xrange.each{|x| yrange.each{|y| s1[x][y] = s2[x][y] - zs(s2,x,y,0)}} # Step 2
|
||||
end until @r == 0
|
||||
printout(s1)
|
||||
end
|
||||
def zs(ng,x,y,g)
|
||||
return 0 if ng[x][y] == 0 or # P1
|
||||
(ng[x-1][y] + ng[x][y+1] + ng[x+g][y-1+g]) == 3 or # P2, P4, P6/P8
|
||||
(ng[x-1+g][y+g] + ng[x+1][y] + ng[x][y-1]) == 3 # P4/P2, P6, P8
|
||||
bp1 = NEIGHBOUR8.inject(0){|res,(i,j)| res += ng[x+i][y+j]} # B(P1)
|
||||
return 0 if bp1 < 2 or 6 < bp1
|
||||
ap1 = CIRCULARS.map{|i,j| ng[x+i][y+j]}.each_cons(2).count{|a,b| a<b} # A(P1)
|
||||
return 0 if ap1 != 1
|
||||
@r = 1
|
||||
end
|
||||
def printout(image)
|
||||
puts image.map{|row| row.map{|col| " #"[col]}.join}
|
||||
end
|
||||
end
|
||||
|
||||
str = <<EOS
|
||||
...........................................................
|
||||
.#################...................#############.........
|
||||
.##################...............################.........
|
||||
.###################............##################.........
|
||||
.########.....#######..........###################.........
|
||||
...######.....#######.........#######.......######.........
|
||||
...######.....#######........#######.......................
|
||||
...#################.........#######.......................
|
||||
...################..........#######.......................
|
||||
...#################.........#######.......................
|
||||
...######.....#######........#######.......................
|
||||
...######.....#######........#######.......................
|
||||
...######.....#######.........#######.......######.........
|
||||
.########.....#######..........###################.........
|
||||
.########.....#######.######....##################.######..
|
||||
.########.....#######.######......################.######..
|
||||
.########.....#######.######.........#############.######..
|
||||
...........................................................
|
||||
EOS
|
||||
|
||||
ZhangSuen.new(str)
|
||||
|
||||
task_example = <<EOS
|
||||
00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000
|
||||
EOS
|
||||
|
||||
ZhangSuen.new(task_example, "1")
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
set data {
|
||||
00000000000000000000000000000000
|
||||
01111111110000000111111110000000
|
||||
01110001111000001111001111000000
|
||||
01110000111000001110000111000000
|
||||
01110001111000001110000000000000
|
||||
01111111110000001110000000000000
|
||||
01110111100000001110000111000000
|
||||
01110011110011101111001111011100
|
||||
01110001111011100111111110011100
|
||||
00000000000000000000000000000000
|
||||
}
|
||||
proc zhang-suen data {
|
||||
set data [string trim $data]
|
||||
while 1 {
|
||||
set n 0
|
||||
incr n [step 1 data]
|
||||
incr n [step 2 data]
|
||||
if !$n break
|
||||
}
|
||||
return $data
|
||||
}
|
||||
proc step {number _data} {
|
||||
upvar 1 $_data data
|
||||
set xmax [string length [lindex $data 0]]
|
||||
set ymax [llength $data]
|
||||
switch -- $number {
|
||||
1 {set cond {(!$P2 || !$P4 || !$P6) && (!$P4 || !$P6 || !$P8)}}
|
||||
2 {set cond {(!$P2 || !$P4 || !$P8) && (!$P2 || !$P6 || !$P8)}}
|
||||
}
|
||||
set hits {}
|
||||
for {set x 1} {$x < $xmax-1} {incr x} {
|
||||
for {set y 1} {$y < $ymax-1} {incr y} {
|
||||
if {[getpix $data $x $y] == 1} {
|
||||
set b [B $data $x $y]
|
||||
if {2 <= $b && $b <= 6} {
|
||||
if {[A $data $x $y] == 1} {
|
||||
set P2 [getpix $data $x [expr $y-1]]
|
||||
set P4 [getpix $data [expr $x+1] $y]
|
||||
set P6 [getpix $data $x [expr $y+1]]
|
||||
set P8 [getpix $data [expr $x-1] $y]
|
||||
if $cond {lappend hits $x $y}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach {x y} $hits {set data [setpix $data $x $y 0]}
|
||||
return [llength $hits]
|
||||
}
|
||||
proc A {data x y} {
|
||||
set res 0
|
||||
set last [getpix $data $x [expr $y-1]]
|
||||
foreach {dx dy} {1 -1 1 0 1 1 0 1 -1 1 -1 0 -1 -1 0 -1} {
|
||||
set this [getpix $data [expr $x+$dx] [expr $y+$dy]]
|
||||
if {$this > $last} {incr res}
|
||||
set last $this
|
||||
}
|
||||
return $res
|
||||
}
|
||||
proc B {data x y} {
|
||||
set res 0
|
||||
foreach {dx dy} {1 -1 1 0 1 1 0 1 -1 1 -1 0 -1 -1 0 -1} {
|
||||
incr res [getpix $data [expr $x+$dx] [expr $y+$dy]]
|
||||
}
|
||||
return $res
|
||||
}
|
||||
proc getpix {data x y} {
|
||||
string index [lindex $data $y] $x
|
||||
}
|
||||
proc setpix {data x y val} {
|
||||
set row [lindex $data $y]
|
||||
lset data $y [string replace $row $x $x $val]
|
||||
return $data
|
||||
}
|
||||
puts [string map {1 @ 0 .} [join [zhang-suen $data] \n]]
|
||||
Loading…
Add table
Add a link
Reference in a new issue