Skip to content

Commit f9edf0e

Browse files
committed
v1std.motion_dog and corresponding test
1 parent 6c516a5 commit f9edf0e

File tree

12 files changed

+286
-23
lines changed

12 files changed

+286
-23
lines changed

examples/motion_dog/motion_dog.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/emer/emergent/v2/edge"
2424
"github.com/emer/v1vision/dog"
2525
"github.com/emer/v1vision/motion"
26+
"github.com/emer/v1vision/v1std"
2627
"github.com/emer/v1vision/v1vision"
2728
)
2829

@@ -94,6 +95,9 @@ type Vis struct { //types:add
9495
// FullField integrated output
9596
FullField tensor.Float32 `display:"no-inline"`
9697

98+
// MotionDoG is a self-contained version of motion dog filtering.
99+
MotionDoG v1std.MotionDoG
100+
97101
fastIdx, starIdx int
98102

99103
tabView *core.Tabs
@@ -113,11 +117,10 @@ func (vi *Vis) Defaults() {
113117
sz := 12 // V1mF16 typically = 12, no border
114118
spc := 4
115119
vi.DoG.SetSize(sz, spc)
116-
// note: first arg is border -- we are relying on Geom
117-
// to set border to .5 * filter size
118-
// any further border sizes on same image need to add Geom.FiltRt!
119120
vi.Geom.Set(math32.Vec2i(0, 0), math32.Vec2i(spc, spc), math32.Vec2i(sz, sz))
120121
vi.Geom.SetImageSize(vi.ImageSize)
122+
vi.MotionDoG.Defaults()
123+
// vi.MotionDoG.GPU = false
121124
}
122125

123126
func (vi *Vis) Config() {
@@ -139,6 +142,7 @@ func (vi *Vis) Config() {
139142
if vi.GPU {
140143
vi.V1.GPUInit()
141144
}
145+
vi.MotionDoG.Config(vi.ImageSize)
142146
}
143147

144148
// RenderFrames renders the frames
@@ -147,17 +151,12 @@ func (vi *Vis) RenderFrames() { //types:add
147151
vi.Motion.NormInteg = 0
148152
vi.Pos = vi.Start
149153
for i := range vi.NFrames {
150-
_ = i
151154
vi.RenderFrame()
152155
vi.Pos = vi.Pos.Add(vi.Velocity)
153156
vi.Filter()
154157
if vi.tabView != nil {
155-
// fmt.Println(i)
156158
vi.tabView.AsyncLock()
157159
vi.tabView.Update()
158-
// vi.fastView.Update()
159-
// vi.slowView.Update()
160-
// vi.imgView.Update()
161160
vi.tabView.AsyncUnlock()
162161
fmt.Printf("%d\tL: %7.4g\tR: %7.4g\tB: %7.4g\tT: %7.4g\tN: %7.4g\n", i, vi.FullField.Value1D(0), vi.FullField.Value1D(1), vi.FullField.Value1D(2), vi.FullField.Value1D(3), vi.Motion.NormInteg)
163162
time.Sleep(vi.FrameDelay)
@@ -183,6 +182,9 @@ func (vi *Vis) RenderFrame() {
183182
// Filter runs the filters on current image.
184183
func (vi *Vis) Filter() error { //types:add
185184
v1vision.UseGPU = vi.GPU
185+
vi.V1.SetAsCurrent()
186+
// note: if switching between different gpu, then zero values get copied
187+
// up to GPU on SetAsCurrent, so need ValuesVar to synchronize.
186188
vi.V1.Run(v1vision.ScalarsVar, v1vision.ValuesVar, v1vision.ImagesVar)
187189
// vi.V1.Run(v1vision.ScalarsVar) // minimal fastest case
188190
vi.Motion.FullFieldInteg(vi.V1.Scalars, &vi.FullField)
@@ -203,6 +205,8 @@ func (vi *Vis) Filter() error { //types:add
203205
vi.Star.SetShapeSizes(int(vi.Geom.Out.Y-1), int(vi.Geom.Out.X-1), 2, 4)
204206
tensor.CopyFromLargerShape(&vi.Star, star)
205207

208+
vi.MotionDoG.RunTensor(vi.ImageTsr)
209+
206210
return nil
207211
}
208212

@@ -221,7 +225,7 @@ func (vi *Vis) ConfigGUI() *core.Body {
221225
s.Range.FixMax = false
222226
})
223227

224-
b := core.NewBody("lgn_dog").SetTitle("LGN DoG Filtering")
228+
b := core.NewBody("lgn_dog").SetTitle("Motion DoG Filtering")
225229
sp := core.NewSplits(b)
226230
core.NewForm(sp).SetStruct(vi)
227231
tb := core.NewTabs(sp)

motion/enumgen.go

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

motion/motion.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ import (
1414

1515
//go:generate core generate -add-types -gosl
1616

17+
// Directions are the motion directions, in feature order,
18+
// as represented in the Star and FullField outputs.
19+
type Directions int32 //enums:enum
20+
21+
const (
22+
Left Directions = iota
23+
Right
24+
Down
25+
Up
26+
)
27+
1728
// Params has the motion parameters for retinal starburst amacrine
1829
// cells (SAC) that compute centrifugal motion flow from each point.
1930
type Params struct {

motion/typegen.go

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v1std/dog_color.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,23 @@ type DoGColor struct {
5151
func (vi *DoGColor) Defaults() {
5252
vi.GPU = true
5353
vi.DoG.Defaults()
54-
sz := 12 // V1mF16 typically = 12, no border -- defaults
55-
spc := 16 // note: not 4; broader blob tuning
56-
vi.DoG.Spacing = spc
57-
vi.DoG.Size = sz
5854
vi.DoG.Gain = 8 // color channels are weaker than grey
5955
vi.DoG.OnGain = 1
6056
vi.DoG.SetSameSigma(0.5) // no spatial component, just pure contrast
61-
vi.Geom.Set(math32.Vec2i(0, 0), math32.Vec2i(spc, spc), math32.Vec2i(sz, sz))
57+
vi.SetSize(12, 16) // V1mF16 typically = 12, no border
6258
vi.KWTA.Defaults()
6359
vi.KWTA.Layer.On.SetBool(false) // non-spatial, mainly for differentiation within pools
6460
vi.KWTA.Pool.Gi = 1.2
6561
}
6662

63+
// SetSize sets the V1sGabor filter size and geom spacing to given values.
64+
// Default is 12, 16, for a medium-sized filter.
65+
func (vi *DoGColor) SetSize(sz, spc int) {
66+
vi.DoG.Spacing = spc
67+
vi.DoG.Size = sz
68+
vi.Geom.Set(math32.Vec2i(0, 0), math32.Vec2i(spc, spc), math32.Vec2i(sz, sz))
69+
}
70+
6771
// Config configures the filtering pipeline with all the current parameters.
6872
// imageSize is the _content_ size of input image that is passed to Run
6973
// as an RGB Tensor (per [V1Vision.Images] standard format),
@@ -80,7 +84,7 @@ func (vi *DoGColor) Config(imageSize image.Point) {
8084
lmsRG := vi.V1.NewImage(vi.Geom.In.V())
8185
lmsBY := vi.V1.NewImage(vi.Geom.In.V())
8286

83-
vi.V1.NewWrapImage(img, 3, wrap, int(vi.Geom.FilterRt.X), &vi.Geom)
87+
vi.V1.NewWrapImage(img, 3, wrap, int(vi.Geom.Border.X), &vi.Geom)
8488
vi.V1.NewLMSComponents(wrap, lmsRG, lmsBY, vi.DoG.Gain, &vi.Geom)
8589

8690
out := vi.V1.NewValues(int(vi.Geom.Out.Y), int(vi.Geom.Out.X), 2)

v1std/dog_grey.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ type DoGGrey struct {
4040
func (vi *DoGGrey) Defaults() {
4141
vi.GPU = true
4242
vi.DoG.Defaults()
43-
spc := 4
44-
sz := 12
43+
vi.SetSize(12, 4)
44+
}
45+
46+
// SetSize sets the V1sGabor filter size and geom spacing to given values.
47+
// Default is 12, 4, for a medium-sized filter.
48+
func (vi *DoGGrey) SetSize(sz, spc int) {
4549
vi.DoG.Spacing = spc
4650
vi.DoG.Size = sz
4751
vi.Geom.Set(math32.Vec2i(0, 0), math32.Vec2i(spc, spc), math32.Vec2i(sz, sz))
@@ -59,7 +63,7 @@ func (vi *DoGGrey) Config(imageSize image.Point) {
5963
img := vi.V1.NewImage(vi.Geom.In.V())
6064
wrap := vi.V1.NewImage(vi.Geom.In.V())
6165

62-
vi.V1.NewWrapImage(img, 0, wrap, int(vi.Geom.FilterRt.X), &vi.Geom)
66+
vi.V1.NewWrapImage(img, 0, wrap, int(vi.Geom.Border.X), &vi.Geom)
6367
_, out := vi.V1.NewDoG(wrap, 0, &vi.DoG, &vi.Geom)
6468
vi.V1.NewLogValues(out, out, 1, 1.0, &vi.Geom)
6569
vi.V1.NewNormDiv(v1vision.MaxScalar, out, out, 1, &vi.Geom)

v1std/motion_dog.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) 2025, The Emergent Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package v1std
6+
7+
import (
8+
"image"
9+
10+
"cogentcore.org/core/math32"
11+
"cogentcore.org/lab/tensor"
12+
"github.com/emer/v1vision/dog"
13+
"github.com/emer/v1vision/motion"
14+
"github.com/emer/v1vision/v1vision"
15+
)
16+
17+
// MotionDoG computes starburst-amacrine style motion processing and
18+
// resulting summary full-field motion values, on greyscale
19+
// difference-of-gaussian (DoG) filtering.
20+
// Call Defaults and then set any custom params, then call Config.
21+
// Results are in Output tensor after Run().
22+
type MotionDoG struct {
23+
// GPU means use the GPU by default (does GPU initialization) in Config.
24+
// To change what is actually used at the moment of running,
25+
// set [v1vision.UseGPU].
26+
GPU bool
27+
28+
// LGN DoG filter parameters.
29+
DoG dog.Filter
30+
31+
// Motion filter parameters.
32+
Motion motion.Params
33+
34+
// Geom is geometry of input, output.
35+
Geom v1vision.Geom `edit:"-"`
36+
37+
// FullField has the integrated FullField output (1D).
38+
// Use [motion.Directions] for 1D indexes (is 2x2 for [L,R][D,U]).
39+
FullField tensor.Float32 `display:"no-inline"`
40+
41+
// GetStar retrieves the star values. Otherwise, just the full-field.
42+
GetStar bool
43+
44+
// V1 is the V1Vision filter processing system.
45+
V1 v1vision.V1Vision `display:"no-inline"`
46+
47+
// Star has the star values, if GetStar is true,
48+
// pointing to Values in V1.
49+
// [Y, X, Polarity, 4], where Polarity is DoG polarity, and 4 is for
50+
// Left, Right, Down, Up.
51+
Star *tensor.Float32 `display:"no-inline"`
52+
}
53+
54+
func (vi *MotionDoG) Defaults() {
55+
vi.GPU = true
56+
vi.DoG.Defaults()
57+
vi.Motion.Defaults()
58+
vi.SetSize(12, 4)
59+
}
60+
61+
// SetSize sets the V1sGabor filter size and geom spacing to given values.
62+
// Default is 12, 4, for a medium-sized filter.
63+
func (vi *MotionDoG) SetSize(sz, spc int) {
64+
vi.DoG.Spacing = spc
65+
vi.DoG.Size = sz
66+
vi.Geom.Set(math32.Vec2i(0, 0), math32.Vec2i(spc, spc), math32.Vec2i(sz, sz))
67+
}
68+
69+
// Config configures the filtering pipeline with all the current parameters.
70+
// imageSize is the _content_ size of input image that is passed to Run
71+
// as an RGB Tensor (per [V1Vision.Images] standard format),
72+
// (i.e., exclusive of the additional border around the image = [Image.Size]).
73+
// The resulting Geom.Border field can be passed to [Image] methods.
74+
func (vi *MotionDoG) Config(imageSize image.Point) {
75+
vi.Geom.SetImageSize(imageSize)
76+
vi.FullField.SetShapeSizes(2, 2)
77+
78+
fn := 1 // number of filters in DoG
79+
80+
vi.V1.Init()
81+
img := vi.V1.NewImage(vi.Geom.In.V())
82+
wrap := vi.V1.NewImage(vi.Geom.In.V())
83+
84+
vi.V1.NewWrapImage(img, 0, wrap, int(vi.Geom.Border.X), &vi.Geom)
85+
_, out := vi.V1.NewDoG(wrap, 0, &vi.DoG, &vi.Geom)
86+
vi.V1.NewLogValues(out, out, fn, 1.0, &vi.Geom)
87+
vi.V1.NewNormDiv(v1vision.MaxScalar, out, out, fn, &vi.Geom)
88+
89+
vi.Motion.DoGSumScalarIndex = vi.V1.NewAggScalar(v1vision.SumScalar, out, fn, &vi.Geom)
90+
fastIdx := vi.V1.NewMotionIntegrate(out, fn, vi.Motion.FastTau, vi.Motion.SlowTau, &vi.Geom)
91+
starIdx := vi.V1.NewMotionStar(fastIdx, fn, vi.Motion.Gain, &vi.Geom)
92+
vi.Motion.FFScalarIndex = vi.V1.NewMotionFullField(starIdx, fn, &vi.Geom)
93+
94+
if vi.GetStar {
95+
out4 := vi.V1.NewValues4D(int(vi.Geom.Out.Y), int(vi.Geom.Out.X), 2, 4)
96+
vi.Star.SetShapeSizes(int(vi.Geom.Out.Y), int(vi.Geom.Out.X), 2, 4)
97+
vi.V1.NewTo4D(starIdx, out4, 2, 4, 0, &vi.Geom)
98+
}
99+
100+
vi.V1.SetAsCurrent()
101+
if vi.GPU {
102+
vi.V1.GPUInit()
103+
}
104+
}
105+
106+
// RunImage runs the configured filtering pipeline
107+
// on given Image, using given [Image] handler.
108+
func (vi *MotionDoG) RunImage(im *Image, img image.Image) {
109+
im.SetImageGrey(&vi.V1, img, int(vi.Geom.Border.X))
110+
vi.Run()
111+
}
112+
113+
// Run runs the configured filtering pipeline
114+
// on given Image tensor.
115+
func (vi *MotionDoG) RunTensor(tsr *tensor.Float32) {
116+
itsr := vi.V1.Images.SubSpace(0).(*tensor.Float32)
117+
itsr.CopyFrom(tsr)
118+
vi.Run()
119+
}
120+
121+
// Run runs the configured filtering pipeline.
122+
// image in vi.V1.Images[0] must already have been set.
123+
func (vi *MotionDoG) Run() {
124+
v1vision.UseGPU = vi.GPU
125+
vi.V1.SetAsCurrent()
126+
127+
vals := []v1vision.GPUVars{v1vision.ScalarsVar, v1vision.ValuesVar}
128+
if vi.GetStar {
129+
vals = append(vals, v1vision.Values4DVar)
130+
}
131+
vi.V1.Run(vals...)
132+
vi.Motion.FullFieldInteg(vi.V1.Scalars, &vi.FullField)
133+
if vi.GetStar {
134+
vi.Star = vi.V1.Values4D.SubSpace(0).(*tensor.Float32)
135+
}
136+
}
137+
138+
// Init resets all motion integration values to 0.
139+
func (vi *MotionDoG) Init() {
140+
vi.V1.SetAsCurrent()
141+
tensor.SetAllFloat64(vi.V1.Values, 0)
142+
tensor.SetAllFloat64(vi.V1.Scalars, 0)
143+
tensor.SetAllFloat64(&vi.FullField, 0)
144+
vi.V1.ToGPUInfra()
145+
}

v1std/v1c_color.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (vi *V1cColor) Config(imageSize image.Point) {
9494
wrap := vi.V1.NewImage(vi.V1sGeom.In.V())
9595
lms := vi.V1.NewImage(vi.V1sGeom.In.V())
9696

97-
vi.fadeOpIdx = vi.V1.NewFadeImage(img, 3, wrap, int(vi.V1sGeom.FilterRt.X), .5, .5, .5, &vi.V1sGeom)
97+
vi.fadeOpIdx = vi.V1.NewFadeImage(img, 3, wrap, int(vi.V1sGeom.Border.X), .5, .5, .5, &vi.V1sGeom)
9898
vi.V1.NewLMSOpponents(wrap, lms, vi.ColorGain, &vi.V1sGeom)
9999

100100
nang := vi.V1sGabor.NAngles
@@ -161,7 +161,7 @@ func (vi *V1cColor) RunImage(im *Image, img image.Image) {
161161
vi.V1.SetAsCurrent()
162162
v1vision.UseGPU = vi.GPU
163163
im.SetImageRGB(&vi.V1, img, int(vi.V1sGeom.Border.X))
164-
r, g, b := v1vision.EdgeAvg(im.Tsr, int(vi.V1sGeom.FilterRt.X))
164+
r, g, b := v1vision.EdgeAvg(im.Tsr, int(vi.V1sGeom.Border.X))
165165
vi.V1.SetFadeRGB(vi.fadeOpIdx, r, g, b)
166166
vi.V1.Run(v1vision.Values4DVar)
167167
vi.Output = vi.V1.Values4D.SubSpace(0).(*tensor.Float32)

0 commit comments

Comments
 (0)