source: cpp/frams/canvas/neurodiagram.cpp @ 147

Last change on this file since 147 was 147, checked in by sz, 10 years ago

This sample code may be useful for people reimplementing the standard framsticks neural network drawing routine (look for NeuroSymbol::paint(), especially the fragment starting at line 283)

  • Property svn:eol-style set to native
File size: 16.1 KB
Line 
1#include "neurodiagram.h"
2#include "nn_layout.h"
3#include <frams/neuro/neurolibrary.h>
4#include <frams/mech/mechworld.h>
5#include <frams/util/multirange.h>
6#include "canvasutil.h"
7#include <frams/neuro/neuroimpl.h>
8#include <frams/model/modelobj.h>
9#include <frams/simul/simul.h>
10#include "common/nonstd_time.h"
11
12#define FIELDSTRUCT NeuroDiagram
13ParamEntry neurodiagram_paramtab[] =
14{
15        { "NeuroDiagram", 1, 4, "NeuroDiagram", "Can be used as the client object in the Window.", },
16        { "new", 0, PARAM_USERHIDDEN, "create new NeuroDiagram", "p oNeuroDiagram", PROCEDURE(p_new), },
17        { "showCreature", 0, PARAM_USERHIDDEN + PARAM_NOSTATIC, "show dynamic NN", "p(oCreature)", PROCEDURE(p_showcr), },
18        { "showModel", 0, PARAM_USERHIDDEN + PARAM_NOSTATIC, "show static NN", "p(oModel)", PROCEDURE(p_showmod), },
19        { "hide", 0, PARAM_USERHIDDEN + PARAM_NOSTATIC, "hide NN", "p()", PROCEDURE(p_hide), },
20        { 0, 0, 0, },
21};
22#undef FIELDSTRUCT
23
24Param neurodiagram_param(neurodiagram_paramtab, 0);
25
26static struct ColorDefs colordefs;
27
28void NeuroDiagram::p_new(ExtValue*args, ExtValue*ret)
29{
30        NeuroDiagram *d = new NeuroDiagram(&colordefs);
31        d->drawbackground = false;
32        ret->setObject(ExtObject(&neurodiagram_param, d));
33}
34
35void NeuroDiagram::p_showcr(ExtValue*args, ExtValue*ret)
36{
37        Creature *cr = 0;
38        if (args->type == TObj)
39        {
40                const ExtObject& o = args->getObject();
41                cr = (Creature*)o.getTarget();
42        }
43        showLive(cr);
44}
45
46void NeuroDiagram::p_showmod(ExtValue*args, ExtValue*ret)
47{
48        Model *mod = ModelObj::fromObject(args[0]);
49        show(mod);
50}
51
52static void addNeuroDescription(SString &t, Neuro *n)
53{
54        static Param par;
55        SString c(n->getClassName());
56        NeuroClass* cl = n->getClass();
57        t += c;
58        t += " (";
59        if (cl)
60                t += cl->getLongName();
61        else
62                t += "Unknown";
63        t += ")";
64}
65
66NeuroDiagram::NeuroDiagram(ColorDefs *cd)
67:FramDrawToolkit(cd), livewire(false), indestructor(false), showing_not_alive_label(false), o(0),
68warn_if_not_alive(true), selection(*this), drawbackground(true), linetype(true), layouttype(2)
69{
70        scroll.setMargin(10, 10); // appropriate size should be adjusted
71        dontPaintOutside(0);
72        add(&scroll);
73        pluginactive = false;
74        FramDrawToolkit::setBackColor(ColorDefs::neurobackground);
75}
76
77NeuroDiagram::~NeuroDiagram()
78{
79        indestructor = 1;
80        hide();
81        remove(&scroll);
82        updatePlugin();
83}
84
85void NeuroDiagram::hide()
86{
87        NeuroProbe *pr;
88        showing_not_alive_label = 0;
89        if (o) o->delmodel_list.remove(killnode);
90        for (probes.start(); pr = (NeuroProbe*)probes();) delete pr;
91        probes.clear();
92        selection.clear(0);
93        cr = 0;
94        livewire = false;
95}
96
97class NNLayoutState_Neurodiagram : public NNLayoutState
98{
99public:
100        NeuroDiagram *nd;
101        NNLayoutState_Neurodiagram(NeuroDiagram *_nd) :nd(_nd) {}
102
103        int GetElements()
104        {
105                return nd->scroll.count();
106        }
107
108        int *GetXYWH(int el)
109        {
110                return &nd->scroll.getInfo(el)->pos.x;
111        }
112
113        void SetXYWH(int el, int x, int y, int w, int h)
114        {
115                ScrollInfo *si = nd->scroll.getInfo(el);
116                si->pos.set(x, y); si->size.set(w, h);
117        }
118
119        int GetInputs(int el)
120        {
121                return nd->getNS(el)->n->getInputCount();
122        }
123
124        int GetLink(int el, int i)
125        {
126                return nd->getNS(el)->n->getInput(i)->refno;
127        }
128
129        int *GetLinkXY(int el, int i)
130        {
131                static int XY[2];
132                int *xywh = GetXYWH(el);
133                XY[0] = 0;
134                XY[1] = ((1 + i)*xywh[3]) / (GetInputs(el) + 1);
135                return XY;
136        }
137};
138
139
140void NeuroDiagram::show(Model *o_)
141{
142        hide();
143        o = o_;
144        scroll.removeAll();
145        if (o)
146        {
147                Neuro *n;
148                int i;
149                killnode = o->delmodel_list.add(STATRICKCALLBACK(this, &NeuroDiagram::onKill, 0));
150
151                // create symbol objects
152                for (i = 0; n = o->getNeuro(i); i++)
153                {
154                        NeuroSymbol *ns = new NeuroSymbol(*this, n);
155                        scroll.add(ns, 1); // autodel   
156                }
157                if (i)
158                {
159                        struct NNLayoutFunction *nnfun = &nn_layout_functions[layouttype];
160                        NNLayoutState_Neurodiagram nn(this);
161                        nnfun->doLayout(&nn);
162                }
163                scroll.invalidate();
164                scroll.autoZoom();
165        }
166
167        updatePlugin();
168        requestPaint();
169}
170
171void NeuroDiagram::showLive(Creature *_cr)
172{
173        showing_not_alive_label = 0;
174        if (!_cr) { show(0); return; }
175        show(&_cr->getModel());
176        cr = _cr;
177        livewire = true;
178        updatePlugin();
179}
180
181void NeuroDiagram::paint()
182{
183        if (drawbackground)
184        {
185                setColor(ColorDefs::neurobackground);
186                clear();
187        }
188
189        if (countNeurons() > 0)
190        {
191                CanvasWindowContainer::paint();
192        }
193        else
194        {
195                setColor(ColorDefs::neuroneuron);
196                drawAlignedText(size.x / 2, (size.y - textHeight()) / 2, 0, "[No neural network]");
197        }
198
199        if (showing_not_alive_label)
200        {
201                if (time(0) > showing_not_alive_label)
202                        showing_not_alive_label = 0;
203                else
204                {
205                        setColor(0, 0, 0);
206                        drawAlignedText(not_alive_location.x, not_alive_location.y, 0, "select a creature");
207                        drawAlignedText(not_alive_location.x, not_alive_location.y + textHeight(), 0, "to probe neurons");
208                }
209        }
210}
211
212void NeuroDiagram::resize(int w, int h)
213{
214        CanvasWindowContainer::resize(w, h);
215        scroll.autoZoom();
216}
217
218int NeuroDiagram::countNeurons()
219{
220        return scroll.count();
221}
222
223void NeuroDiagram::addProbe(int i)
224{
225        if (i >= countNeurons()) return;
226        NeuroProbe *probe = new NeuroProbe(getNS(i));
227        probes += (void*)probe;
228        add(probe);
229        updatePlugin();
230        requestPaint();
231}
232
233void NeuroDiagram::onKill(void*obj, long dummy)
234{
235        show(0);
236}
237
238///////////////////////////
239
240NeuroSymbol::NeuroSymbol(NeuroDiagram &nd, Neuro * _n)
241:selected(0), n(_n), diagram(nd)
242{
243        tooltip = "#";
244        tooltip += SString::valueOf((int)n->refno);
245        tooltip += " - ";
246        label = tooltip;
247        addNeuroDescription(tooltip, n);
248        label += n->getClassName();
249        if (n->getClassParams().len())
250        {
251                tooltip += "\n"; tooltip += n->getClassParams();
252        }
253}
254
255void NeuroSymbol::paint()
256{
257        if (selected)
258        {
259                setColor(255, 255, 255);
260                fillRect(0, 0, size.x, size.y);
261        }
262        diagram.setClip();
263        diagram.setColor(ColorDefs::neuroneuron);
264        drawNeuroSymbol(this, n->getClass(), 0, 0, size.x, size.y);
265
266        if (size.y > 4 * textHeight())
267        {
268                const char* t = label;
269                drawAlignedText(size.x / 2, size.y - textHeight(), 0, t);
270
271                NeuroImpl *ni = NeuroNetImpl::getImpl(n);
272                if (ni && (ni->getChannelCount() > 1))
273                {
274                        drawLine(size.x - size.x / 16, size.y / 2 - size.y / 16,
275                                size.x - size.x / 8, size.y / 2 + size.y / 16);
276                        char t[20];
277                        sprintf(t, "%d", ni->getChannelCount());
278                        moveTo(size.x, size.y / 2 - textHeight());
279                        drawText(t);
280                }
281        }
282
283        // NeuroSymbol is also responsible for drawing connection lines from its inputs to other NeuroSymbols' outputs
284        NeuroSymbol *ns2;
285        if (!diagram.isLive())
286                diagram.setColor(ColorDefs::neurolink);
287        for (int iw = 0; iw < n->getInputCount(); iw++)
288        {
289                ns2 = diagram.getNS(n->getInput(iw)->refno);// the other NeuroSymbol (our input will connecto to its output)
290
291                int yw = inputY(iw);
292                int xw = yw / 4;// x coordinate of the first corner point, depends on yw to avoid overlapping between inputs
293                drawLine(size.x / 4, yw, xw, yw); // first horizontal segment (to the left)
294                diagram.setWireColor(ns2->n->state, 0);
295                if ((diagram.linetype != 1) || (ns2->pos.x + ns2->size.x / 2 < pos.x))
296                { // straight line to the other neuron's output (signal goes forwards)
297                        ns2->lineTo(ns2->size.x, ns2->size.y / 2);
298                }
299                else
300                { // make a loop from 3 segments - vert/horiz/vert (signal goes backwards)
301                        int y2;
302                        int down;
303                        if (ns2 == this) down = (iw >= ((n->getInputCount()) / 2)); else down = (ns2->pos.y > (pos.y + size.y));
304                        if (down)
305                        {
306                                y2 = (pos.y + size.y + (size.y - yw) / 3);
307                        }
308                        else
309                        {
310                                y2 = pos.y - yw / 3;
311                                if ((ns2->pos.y<pos.y) && (ns2->pos.y>(pos.y - ns2->size.y))) y2 -= pos.y - ns2->pos.y;
312                        }
313                        // note: "diagram" uses global coordinate system, so we add "pos" or "ns2->pos" to get NeuroSymbol's global positions
314                        diagram.lineTo(pos.x + xw, y2);
315                        diagram.lineTo(ns2->pos.x + ns2->size.x, y2);
316                        diagram.lineTo(ns2->pos.x + ns2->size.x, ns2->pos.y + ns2->size.y / 2);
317                }
318
319        }
320}
321
322void NeuroSymbol::mouse(int x, int y, int t)
323{
324        if ((t & (LeftButton | ShiftButton)) == (LeftButton | ShiftButton))
325        {
326                ScrollManager& sc = diagram.scroll;
327                sc.setPos2(n->refno, pos.x + x - diagram.symboldragpos.x, pos.y + y - diagram.symboldragpos.y);
328                sc.validate();
329                requestPaint();
330        }
331}
332
333int NeuroSymbol::mouseclick(int x, int y, int t)
334{
335        if ((t & (LeftButton | DblClick)) == (LeftButton | DblClick))
336        {
337                if (diagram.isLive())
338                        diagram.addProbe(n->refno);
339                else
340                {
341                        if (diagram.warn_if_not_alive)
342                        {
343                                diagram.showing_not_alive_label = time(0) + 10;
344                                diagram.not_alive_location.x = pos.x + x;
345                                diagram.not_alive_location.y = pos.y + y;
346                                diagram.requestPaint();
347                        }
348                }
349                return LeftButton | DblClick;
350        }
351
352        if ((t & (LeftButton | ShiftButton)) == (LeftButton | ShiftButton))
353        {
354                if (selected)
355                        diagram.selection.remove(Model::neuroToMap(n->refno));
356                else
357                        diagram.selection.add(Model::neuroToMap(n->refno));
358                diagram.symboldragpos.set(x, y);
359                return LeftButton | ShiftButton;
360        }
361
362        if (t & LeftButton)
363        {
364                diagram.selection.set(Model::neuroToMap(n->refno));
365                return LeftButton;
366        }
367
368        return 0;
369}
370
371// coordinate y of i-th input
372int NeuroSymbol::inputY(int i)
373{
374        return (1 + i)*size.y / ((n->getInputCount()) + 1);
375}
376
377SString NeuroSymbol::hint(int x, int y)
378{
379        if ((y >= 0) && (y < size.y))
380                if (x<size.x / 4)
381                { // inputs?
382                        if (n->getInputCount()>0)
383                        {
384                                int i = (y*n->getInputCount()) / size.y;
385                                double w;
386                                Neuro* target = n->getInput(i, w);
387                                if (target)
388                                {
389                                        SString t = "connected to #";
390                                        t += SString::valueOf((int)target->refno);
391                                        t += " - ";
392                                        addNeuroDescription(t, target);
393                                        //              if (w!=1.0)
394                                        {
395                                                t += ", weight=";
396                                                t += SString::valueOf(w);
397                                        }
398                                        return t;
399                                }
400                        }
401                }
402        return CanvasWindow::hint(x, y);
403}
404
405/////////////////////////
406
407NeuroProbe::NeuroProbe(NeuroSymbol* ns)
408:DCanvasWindow(DCanvasWindow::Title + DCanvasWindow::Border + DCanvasWindow::Close + DCanvasWindow::Size,
409(const char*)ns->getLabel(), &neurochart, &neurochart)
410{
411        holdismine = 0;
412        drawing = 0; whichdrawing = -1;
413        clientbordersset = 0;
414        adjustingvalue = 0;
415        link = ns;
416        tooltip = SString("Probe for ") + ns->tooltip;
417        setPos(ns->getPos().x, ns->getPos().y);
418        neurochart.printMinMax(0);
419        neurochart.data.setMinMax(-1, 1);
420        chnum = 1; chnum2 = 0; chsel = 0;
421        chselwidth = 0;
422        chselecting = 0;
423        updateChannelCount(NeuroNetImpl::getImpl(link->n));
424}
425
426void NeuroProbe::onClose()
427{
428        link->diagram.probes -= this;
429        delete this;
430}
431
432NeuroProbe::~NeuroProbe()
433{
434        if (holdismine)
435                link->n->flags &= ~Neuro::HoldState;
436}
437
438void NeuroProbe::paint()
439{
440        static char t[40];
441        if (!clientbordersset)
442        {
443                clientbordersset = 1;
444                setClientBorder(0, 1, 16, textHeight() + 2); // don't use textheight outside paint/mouse events
445        }
446        int hold = link->n->flags & Neuro::HoldState;
447        float state = (float)link->n->state;
448        NeuroImpl *ni = 0;
449        if (chsel != 0)
450        {
451                ni = NeuroNetImpl::getImpl(link->n);
452                if (chsel < 0)
453                {
454                        int dr = -chsel - 1;
455                        if (whichdrawing != dr)
456                        {
457                                drawing = ni->getDrawing(dr);
458                                whichdrawing = dr;
459                        }
460                        if (drawing)
461                        {
462                                int *dr = drawing;
463                                int w = size.x - 2, h = size.y - clienttop - clientbottom;
464                                int scale = min(w, h);
465                                int x0 = clienttop + leftborder + ((w > h) ? (w - h) / 2 : 0);
466                                int y0 = clientleft + topborder + ((h > w) ? (h - w) / 2 : 0);
467
468                                while (*dr != NeuroImpl::ENDDRAWING)
469                                {
470                                        int first = 1;
471                                        unsigned char r, g, b;
472                                        FramDrawToolkit::splitRGB(*(dr++), r, g, b);
473                                        setColor(r, g, b);
474                                        while (*dr != NeuroImpl::ENDDRAWING)
475                                        {
476                                                int x = ((*(dr++))*scale) / (NeuroImpl::MAXDRAWINGXY + 1) + x0;
477                                                int y = ((*(dr++))*scale) / (NeuroImpl::MAXDRAWINGXY + 1) + y0;
478                                                if (first) { moveTo(x, y); first = 0; }
479                                                else lineTo(x, y);
480                                        }
481                                        dr++;
482                                }
483                        }
484                }
485        }
486        DCanvasWindow::paintWithClient((chsel < 0) ? 0 : client);
487        setColor(0, 0, 0);
488        int yline = size.y - 2;
489        if (chsel >= 0)
490        {
491                if (ni) state = (float)ni->getState(chsel);
492                yline -= textHeight();
493                int y = mapClientY(neurochart.mapData(state));
494                int x = size.x - 15 - 1;
495                drawLine(1, yline, size.x - 2, yline);
496                if (hold)
497                {
498                        sprintf(t, "hold: %1.3g", state);
499                        fillRect(x, y - 1 - 5, 15, 3 + 5 + 5);
500                        setColor(255, 0, 0);
501                        fillRect(x + 2, y - 1, 15 - 2 - 2, 3);
502                }
503                else
504                {
505                        sprintf(t, "signal: %1.3g", state);
506                        fillRect(x, y - 1, 15, 3);
507                }
508                drawAlignedText(size.x - textHeight(), yline, 1, t);
509        }
510
511        if ((chnum > 1) || (chnum2 > 0))
512        {
513                if (chselecting) setColor(255, 255, 255); else setColor(0, 70, 0);
514                if (chsel < 0)
515                        sprintf(t, "%c/%c", 'A' - chsel - 1, 'A' + chnum2 - 1);
516                else
517                        sprintf(t, "%d/%d", chsel, chnum);
518                moveTo(0, yline - textHeight());
519                chselwidth = textWidth(t);
520                drawText(t, -1, getSize().x);
521        }
522        else
523                chselwidth = 0;
524}
525
526void NeuroProbe::mouse(int x, int y, int b)
527{
528        if (chselecting)
529        {
530                int ch = chsel0 + (x - chselx0) / 10;
531                if (selectChannel(ch)) requestPaint();
532                b &= ~LeftButton;
533        }
534        DCanvasWindow::mouse(x, y, b);
535        if (adjustingvalue)
536        {
537                double st = neurochart.unmapData(unmapClientY(y));
538                if (st<-1.0) st = -1.0; else if (st>1.0) st = 1.0;
539                if (chsel == 0)
540                        link->n->state = st;
541                else if (chsel >= 0)
542                {
543                        NeuroImpl *ni = NeuroNetImpl::getImpl(link->n);
544                        if (ni) ni->setCurrentState(st, chsel);//tu bylo odwrotnie!
545                }
546                requestPaint();
547        }
548}
549
550void NeuroProbe::mouseunclick(int x, int y, int b)
551{
552        adjustingvalue = 0;
553        chselecting = 0;
554        DCanvasWindow::mouseunclick(x, y, b);
555}
556
557bool NeuroProbe::insideChSelector(int x, int y)
558{
559        if ((x > 0) && (x < chselwidth))
560        {
561                int sy = size.y;
562                if (chsel >= 0) sy -= textHeight();
563                return ((y<sy) && (y>(sy - textHeight())));
564        }
565        return 0;
566}
567
568int NeuroProbe::mouseclick(int x, int y, int b)
569{
570        if ((b & LeftButton) && insideChSelector(x, y))
571        {
572                chselx0 = x; chsel0 = chsel;
573                chselecting = 1;
574                requestPaint();
575                return LeftButton;
576        }
577        int ret = DCanvasWindow::mouseclick(x, y, b);
578        if (ret)
579        {
580                link->diagram.selection.set(Model::neuroToMap(link->n->refno));
581                return ret;
582        }
583        if (b & LeftButton)
584        {
585                if (x > size.x - 16)
586                {
587                        link->n->flags |= Neuro::HoldState;
588                        holdismine = 1;
589                        adjustingvalue = 1;
590                        mouse(x, y, b);
591                        return LeftButton;
592                }
593                else if (y > size.y - 16)
594                {
595                        link->n->flags ^= Neuro::HoldState;
596                        holdismine = ((link->n->flags&Neuro::HoldState) != 0);
597                        requestPaint();
598                        return LeftButton;
599                }
600        }
601        return 0;
602}
603
604SString NeuroProbe::hint(int x, int y)
605{
606        if ((chsel >= 0) && (x<size.x - 16) && (y>size.y - 16))
607                return SString((link->n->flags&Neuro::HoldState) ? "Click to release" : "Click to hold");
608        else if (insideChSelector(x, y))
609                return SString::sprintf("channel %d of %d (click and drag to switch channels)", chsel, chnum);
610        return DCanvasWindow::hint(x, y);
611}
612
613/** @return true == channel changed */
614bool NeuroProbe::selectChannel(int ch)
615{
616        if (ch < -chnum2) ch = -chnum2; else if (ch >= chnum) ch = chnum - 1;
617        if (ch == chsel) return false;
618        chsel = ch;
619        neurochart.data.clear();
620        return true;
621}
622
623void NeuroProbe::updateChannelCount(NeuroImpl *ni)
624{
625        if (!ni) return;
626        chnum = ni->getChannelCount();
627        chnum2 = ni->getDrawingCount();
628        if (chsel >= chnum) selectChannel(chnum - 1);
629        if (chsel < -chnum2) selectChannel(-chnum2);
630}
631
632void NeuroProbe::sampling()
633{
634        NeuroImpl *ni = NeuroNetImpl::getImpl(link->n);
635        updateChannelCount(ni);
636        if (!chsel)
637                neurochart.data += (float)(link->n->state);
638        else
639                neurochart.data += (float)(ni->getState(chsel));
640        whichdrawing = -1;
641}
642
643////
644
645void NeuroDiagram::probeSampling(void*obj, long dummy)
646{
647        NeuroProbe *pr;
648        for (probes.start(); pr = (NeuroProbe*)probes();) pr->sampling();
649        requestPaint();
650}
651
652void NeuroDiagram::updatePlugin()
653{
654        //int needplugin=(!probes)>0;
655        bool needplugin = livewire;
656        if (needplugin == pluginactive) return;
657        if (needplugin)
658        {
659                if (!cr) return;
660                sim = cr->group->getLibrary().sim;
661                pluginnode = sim->l_plugin.add(STATRICKCALLBACK(this, &NeuroDiagram::probeSampling, 0));
662        }
663        else
664                sim->l_plugin.remove(pluginnode);
665        pluginactive = needplugin;
666}
667
668/////////////
669
670void NeuroDiagramSelection::updateSelection(MultiRange& newsel)
671{
672        MultiRange added = getAdded(newsel);
673        if (!added.isEmpty())
674        {
675                added.shift(Model::mapToNeuro(0));
676                added.intersect(0, diagram.countNeurons() - 1);
677                for (int i = 0; i < added.rangeCount(); i++)
678                {
679                        const IRange &r = added.getRange(i);
680                        for (int j = r.begin; j <= r.end; j++)
681                                diagram.getNS(j)->selected = 1;
682                }
683        }
684        MultiRange removed = getRemoved(newsel);
685        if (!removed.isEmpty())
686        {
687                removed.shift(Model::mapToNeuro(0));
688                removed.intersect(0, diagram.countNeurons() - 1);
689                for (int i = 0; i < removed.rangeCount(); i++)
690                {
691                        const IRange &r = removed.getRange(i);
692                        for (int j = r.begin; j <= r.end; j++)
693                                diagram.getNS(j)->selected = 0;
694                }
695        }
696        if (!diagram.indestructor) diagram.requestPaint();
697}
Note: See TracBrowser for help on using the repository browser.