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

Last change on this file since 147 was 147, checked in by sz, 6 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.