Giter Club home page Giter Club logo

pyncs's People

Contributors

eneftci avatar lorenzmuller avatar sheiksadique avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyncs's Issues

Please add amwhatley as a collaborator

Hi! I'm finally ready to perform the repository changes we discussed at CapoCaccia last year, i.e. the development branch becomes the new master and so on.

Could you please add me as a collaborator such that I can push to the repository?
Thanks,

Adrian

spiketrains_poisson, AttributeError: 'module' object has no attribute 'choice'

on branch: no_syn_ad.

np.random.choice() is new in version 1.7.0, prior versions are still used on many machines, so a backward compatibility would be nice.

/home/emre/ISNL/lib/python2.7/site-packages/pyNCS/group.pyc in spiketrains_poisson(self, rate, t_start, duration, channel)
562 spiketimes = STCreate.poisson_generator(rate=sum(rate),
563 t_start=t_start, t_stop=t_start + duration).spike_times
--> 564 assignments = np.random.choice(self.laddr, len(spiketimes),
565 p=1.*rate/sum(rate))
566 stStim = SpikeList(np.column_stack((assignments, spiketimes)),

AttributeError: 'module' object has no attribute 'choice'

stimulus problem

Hello pyncs developers,

I try to use this command in pyncs but it seems to have some bugs: stimulus = input_addrgroup.spiketrains_regular(freaq,duration).

The manual says that the default duration is 1000.0 ms but with with this duration I can not get any spike.
Example1:
In [51]: stimulus = input_addrgroup.spiketrains_regular(100,1000.0)

In [52]: output = setup.run(stimulus)
Connecting to localhost

if you put 100. instead of 1000.0, stimulus constains some spikes.
Also without speciying any default duration, it works fine for low frequencies.
Example2:
In [49]: stimulus = input_addrgroup.spiketrains_regular(100)

In [50]: output = setup.run(stimulus)
Connecting to localhost
Ch1: 118 evs

However when you put high frequencies it saturates.
Example3:
In [58]: stimulus = input_addrgroup.spiketrains_regular(500)

In [59]: output = setup.run(stimulus)
Connecting to localhost
Ch1: 213 evs

Example3:
In [62]: stimulus = input_addrgroup.spiketrains_regular(700)

In [63]: output = setup.run(stimulus)
Connecting to localhost
Ch1: 235 evs

You can see that although input freaq is 700 hundred we see 235 Hz and we also measure this from oscilloscope.

Could you please look into this problem, it is really urgent.
Thanks/Mehmet/INI

Resetting the biases to default initial conditions

In my experiments, I am running the simulations in a loop for a different stimuli. When I tried to run the loop for the same stimuli for n times, the charge gets buildup on the DPI capacitor, especially when my currents are small and slow. I would like to reset the cap, for my every run and I tried many possibilities within the loop(marked with #tags in my code), but nothing worked out except when I actually exit my ipython shell. So is there any better way of doing this with inbuilt functions? I attached a raster showing my simulations for the same stimuli for different runs.
newproblem

This is my code...

i=0
k=1
while i<=size(dBurst)-1:
j=0
while j<=size(pBurst)-1:

    nsetup.chips['ifslwta0'].load_parameters('biases/defaultBiases_ifslwta_cricket')

    nsetup.chips['if2dwta0'].load_parameters('biases/defaultBiases_if2dwta')

    nsetup.chips['if2dwta1'].load_parameters('biases/defaultBiases_if2dwta')

    nsetup.chips['ifslwta0'].set_parameters(biases_ifslwta0) #setting biases for each run
    nsetup.chips['if2dwta0'].set_parameters(biases_if2dwta0) #setting biases for each run
    nsetup.chips['if2dwta1'].set_parameters(biases_if2dwta1) #setting biases for each run

    nBurst = (burst_period+(burst_period%(dBurst[i]+pBurst[j])))/(dBurst[i]+pBurst[j]) #decides the no. of pulses and pauses within 250 ms period
    while (nBurst*(dBurst[i]+pBurst[j]))-pBurst[j] <= burst_period:
        nBurst = nBurst+1
    nBurst = nBurst-1


    t, rates = create_bursts(fBurst,dBurst[i],pBurst[j],nBurst,nNeu) #passing arguments (fBurst,dBurst,pBurst,nBurst,nNeu)
    print 'nBurst:', nBurst
    stStim=pop_input.soma.spiketrains_inh_generator(rates,t,base_generator=pyST.STCreate.regular_generator)
    out=nsetup.stimulate(stStim)
    nsetup.run(stStim, duration=(nBurst*(dBurst[i]+pBurst[j])))
##Here comes the part that I tried so far
    #l=0

    #while l<=size(pBurst)-1:

     #     t2, rates2 = create_bursts(fBurst,300,300,10,len(pop_input2)) #passing arguments (fBurst,dBurst,pBurst,nBurst,nNeu)
     #     stStim2=pop_input2.soma.spiketrains_inh_generator(rates2,t2,base_generator=pyST.STCreate.regular_generator)
     #     out=nsetup.stimulate(stStim2)
     #     nsetup.run(stStim2, duration=(10*(300+300)))
     #     l=l+1



    #nsetup.mapper.clear_mappings()


    #print "START TIME: %s" % time.ctime()
    #time.sleep(60)
    #print "END TIME : %s" % time.ctime()


    #resetbiases_if2dwta0 = { 'psynaerexctau0': 0,  # Time constant exc0 (AN1b synapse) #2.95>>>>>>>>>>
    #                         'psynaerexctau1': 0,
                           }


    #resetbiases_if2dwta1 = { 'psynaerexctau0': 0,  # Time constant exc0 (AN1b synapse) #2.95>>>>>>>>>>
    #                         'psynaerexctau1': 0,
                           }


    #resetbiases_ifslwta0 = { 'psynstdtau': 0,  # Time constant exc0 (AN1b synapse) #2.95>>>>>>>>>>
    #                  }


    #nsetup.chips['ifslwta0'].set_parameters(resetbiases_ifslwta0) #setting biases for each run
    #nsetup.chips['if2dwta0'].set_parameters(resetbiases_if2dwta0) #setting biases for each run
    #nsetup.chips['if2dwta1'].set_parameters(resetbiases_if2dwta1) #setting biases for each run


    #nsetup.prepare() # Apply the mapping and prepare the network


    j=j+1
    k=k+1

i=i+1

1-to-n connectivity

I have been trying to implement a connectivity pattern where first neuron of population A connects to neuron 1:5 of population B, second neuron (from pop. A) connects to 3:6, third to 5:8 and so on - essentially a 1-to-n connectivity.

To implement I thought of an appropriate 'fashion', and I can see a few of them defined in the source file. Since this is a bit specific, I chose one-to-one, and made the connections separately by a loop where each connection (neuron in a population) would be picked separately. To check if it works, I initially define an src and dest populations of five neurons

my_pop.populate_by_number( nsetup, 'ifslwta', 'excitatory', 5)
input_pop.populate_by_number(nsetup, 'seq', 'excitatory', 5)

and then made the connection for only two (just to test it):

C = pyNCS.Connection(input_pop[0], my_pop[1], 'excitatory0','one2one')
C1 = pyNCS.Connection(input_pop[0], my_pop[2], 'excitatory0','one2one')

But it does not seem to pick up swiftly for the second connection (C1) connection (at mapping.merge).

I need some suggestion if another fashion would make more sense, (e.g. connect_by_binary_matrix etc.) or some other quick fix to the problem. Do we have examples for matrix connectivities?

LOG:

ValueError Traceback (most recent call last)
/usr/lib/python2.7/dist-packages/IPython/utils/py3compat.pyc in execfile(fname, *where)
173 else:
174 filename = fname
--> 175 builtin.execfile(filename, *where)

/homes/saamir/git/fishmorph/experiments/ellTest2/expRun.py in ()
2
3 from pylab import *
----> 4 from expMap import *
5 from expStim import *
6 #from expPlot import *

/homes/saamir/git/fishmorph/experiments/ellTest2/expMap.py in ()
6
7 C = pyNCS.Connection(input_pop[0], my_pop[1], 'excitatory0','one2one')
----> 8 C1 = pyNCS.Connection(input_pop[0], my_pop[2], 'excitatory0','one2one')
9 print "pyMap clear!"
10

/homes/saamir/.local/lib/python2.7/site-packages/pyNCS/connection.pyc in init(self, popsrc, popdst, synapse, fashion, fashion_kwargs, append, setup)
38 setup = popsrc.setup
39 if append:
---> 40 setup.mapping.merge(self.mapping)
41
42 self.synapse = synapse

/homes/saamir/.local/lib/python2.7/site-packages/pyNCS/mapping.pyc in merge(self, pyncs_mapping)
96 if len(pyncs_mapping.mapping) > 0:
97 self.mapping = np.concatenate([self.mapping,
---> 98 pyncs_mapping.mapping]).tolist()
99 else:
100 self.mapping = pyncs_mapping.mapping

ValueError: array dimensions must agree except for d_0


Documentation (Docstrings) for connection fashion

quoting Sadiques e-mail:

"Unfortunately there is no easy way to find out which types of connection 'fashions' you have available to you.

The easiest way to inspect which fashions you have available to you is by doing the following

once you have initialized the NeuroSetup for your experimental setup in ipython do the following

$ setup.mapping.connect_
pyNCS.Mapping.__connect_all2all

pyNCS.Mapping.connect_by_probability_matrix
pyNCS.Mapping.connect_shuffle_all2all
pyNCS.Mapping.connect_by_arbitrary_matrix
pyNCS.Mapping.connect_one2one
pyNCS.Mapping.connect_by_binary_matrix
pyNCS.Mapping.connect_random_all2all

Hope that helps.. In the mean time, we will try and fix the bug and update the documentation about this stuff." <------ :)

AddrGroup: spiketrain generators (and _inh_poisson in particular)

Hi there,

there is a problem with generating spike trains with inhomogeneous rates.

The function spiketrains_inh_poisson returns criptic error (for the end user I mean) when base_generator is passed as argument. This is because a base_generator argument is already internally passed to spiketrains_inh_generator function. The error is a TypeError and the user can't understand the problem without having a look to the code...

If spiketrains_inh_poisson function is used the user shouldn't be allowed to specify a base_generator at all, for obvious reasons.

I propose to remove the kw_args from spiketrains_inh_poisson, because in this context it can cause problems and it's difficult to debug. If the user needs to specify a different generator or he wants to also pass more arguments to the generator then spiketrains_inh_generator should be used instead. In this case spiketrains_inh_poisson becomes just a shortcut, as I think it was supposed to be.

I see this problem related to INI specific usage of spiketrain generator (pyST and co.) so I think we should worry about what would happen to people using different interfaces and spiketrains-related packages.

What do you think?

interactive visualization of mappings: xdot?

As you know, the Connection class contains "dot" representations of the connectivity between populations, and that's what introduced the dependency on pydot package. The "dot" representation has been chosen for its compact, easy to read representation, which can be also easily converted into any other more complex representation. On the other hand, until now it has only been possible to dump this representation into a file and then call the 'dot' program from bash to generate a png of the connectivity graph. No interactive GUI for mappings and plots has ever been possible with this approach.

I just found a package that could enhance user experience by providing graphical visualization of the mappings. It turns out that the tools have always been there in the same graphviz package from where pydot was taken in first place... I think xdot package could be the solution.

http://code.google.com/p/jrfonseca/wiki/XDot#Features

Pros of xdot:

  • Since it doesn't use bitmaps it is fast and has a small memory footprint.
  • Arbitrary zoom.
  • Keyboard/mouse navigation.
  • Supports events on the nodes with URLs.
  • Animated jumping between nodes.
  • Highlights node/edge under mouse.
  • It (seems to) only require pyGTK.
  • Can be used both as a standalone python program and as an embedded part of pyNCS.

Other projects:

Both the above alternatives seem much bigger projects. The latter uses pygame but given the simplicity of the dot representation I would stick with the slickest solution, i.e. xdot.

What do you think?

Fabio

Stimulator / Sequencer object

For stimulating the chip, it is useful to have a Stimulator (or Sequencer) class, that is similar in concept to the Monitor class.

Basic implementation of the API

After discussion with Fabio, we thought of implementing a basic function that uses the API (for release version 1.0)
For example a python module that reads and writes to a FIFO.
Later, we can extend this to a virtual chip simulated for example in Brian.

Sorting in AddrGroup, Population objects

I just went through some of the code in Population class, and there are calls to sort addresses everywhere.

Is sorting really important ?
Why should it be done ?

The reason I ask is..

  1. Lets assume for some reason, I made a fancy arrangement of my neurons that is not logical or physical address sorted. Now when I try to populate the synapses, all of a sudden all the population gets rearranged. Now weather or not one should choose to have weird arrangements of neurons is a whole different issue. But assuming it does matter to some one, the current implementation breaks it down.

  2. Isn't sorting expensive ? So why bother ? (unless ofcourse the user explicitly wants something to be sorted.)

Now, IF i were to comment out all the sorting calls, will the implementation break down ?

Use Physical address inplace of logical addresses for SpikeLists

The NeuroSetup.stimulate() function expects a dictionary of SpikeLists. The SpikeLists use logical addresses as ids. On function call, these addresses are translated to physical addresses and sent to the setup. The same happens backwards on the readout of activity from the setup.

All of this process involves considerable computation and for stimulus spanning across large populations slows down the stimulation and readout process. This is especially visible when you want to do real-time demos.

Since the LogicalAddressing is really only for the purposes of plotting, why not just use the PhysicalAddresses for creating and sending the stimulus ? This has several advantages

  1. No processing required at the time of stimulation or readout. You only require to process the spikes when you are interested in visualizing the spikes.
  2. The Input object used to stimulate is very cryptic as it is now {channel:SpikeList}, since the true essence of pyNCS is to not bother users about the channel information, a simple SpikeList would be better input representation.
  3. This was earlier not possible since ligical addresses are not unique across a multichip setup. But with the use of PhysicalAddresses a single SpikeList object that includes all the input spikes is easily possible.

I must also emphasize that if you all agree to this suggestion, we need to make a SMOOTH transition and make it optional for now because there is a good chance that this enhancement will break old code (although it was intended for the greater good of mankind ;) ).

Use monitors with spike lists

It would be nice to use the monitors functions (e.g. RasterPlot) without going through a neurosetup, but by directory providing lists of spikelists.

load_parameters() fails with error: no attribute 'params'

The following codes are my setup file:

This setup file initializes the setup

Import libraries

import pyNCS
import pyNCS.neurosetup
from pylab import show
import pyST

Define file paths

prefix='./chipfiles/'
setuptype = 'setupfiles/4bit_setuptype.xml'
setupfile = 'setupfiles/zenzero.xml'

Initialize setup

nsetup = pyNCS.neurosetup.NeuroSetup(setuptype,setupfile,prefix=prefix)

clear mapper

nsetup.mapper.clear_mappings();

chip1 = nsetup.chips['ifslwta'];
nsetup.chips['ifslwta'].load_parameters('biases/Biases_ifslwta');

However, there is a mistake in the last line: nsetup.chips['ifslwta'].load_parameters('biases/Biases_ifslwta');

ERROR: An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line statement', (8, 0))


AttributeError Traceback (most recent call last)

/home/october/Experiment/youH2/initSetup.py in ()
19
20 chip1 = nsetup.chips['ifslwta'];
---> 21 nsetup.chips['ifslwta'].load_parameters('biases/Biases_ifslwta');
22
23 #chip2 = nsetup.chips['ifslwta_2'];

/usr/local/lib/python2.7/dist-packages/pyNCS/chip_v2.pyc in load_parameters(self, CSVfile, sep)
429 x = line.strip().split(sep)
430 b[x[0]] = float(x[1])
--> 431 self.set_parameters(b)
432 print 'Parameters loaded and set'
433 return None

/usr/local/lib/python2.7/dist-packages/pyNCS/chip_v2.pyc in set_parameters(self, param_dict)
377 '''
378 with self.context_open():
--> 379 r = self.configurator.set_parameters(param_dict)
380 return r
381

/usr/local/lib/python2.7/dist-packages/pyNCS/init.pyc in f(_args, *_kwargs)
64 @wraps(self.mthd, assigned=('name', 'module'))
65 def f(_args, *_kwargs):
---> 66 return self.mthd(obj, _args, *_kwargs)
67
68 return self.use_parent_doc(f, overridden)

/usr/local/lib/python2.7/dist-packages/pyAMDA/api.pyc in set_parameters(self, param_dict)
40 def set_parameters(self, param_dict):
41 #This function is provided by the API, but it is implemented for performance issues

---> 42 self.__setBias(param_dict)
43
44 @doc_inherit

/usr/local/lib/python2.7/dist-packages/pyAMDA/api.pyc in __setBias(self, param_dict)
78 with self.context_get_param():
79 bias = self.parameters[param_name]
---> 80 x = self.__setValue(param_name, param_value)
81
82 #Commit the bias type (DAC or Bias Generator)

/usr/local/lib/python2.7/dist-packages/pyAMDA/api.pyc in __setValue(self, biasname, value)
122 with self.context_get_param():
123 bias = self.parameters[biasname]
--> 124 norm = self.norm[bias.params['BiasType'].upper()]
125 if value >= 0 and value <= norm:
126 x = self.client.setchannel(bias.params['Channel'],

AttributeError: 'Parameter' object has no attribute 'params'
WARNING: Failure executing file: <initSetup.py>

Do you know what is the problem?

  • Hongzhi

Raw experimental data recording

It would be nice to have a raw data recording mechanism for saving raw experimental data. This can be very useful for depositing experimental data used for a publication. Optionally, the user can generate a description of address scheme that can allow third parties to reproduce the analysis in the paper without using any custom software. This can be generated using the XML files that describe the chip. Perhaps the best way to do this is to use a file FIFO.

Because this should be handled in pyNCS, this might be redundant with the communicator modules inner mechanisms. So one possibility is to add this to the API as a raw_record function. If the module does not support the raw_record function, then pyNCS will generate it automatically (and possibly redundantly.

input spikeList format

So one issue we just solved was some confusion about the input spike list, when passed to the function: input_pop.soma.spiketrains

So, in the spikelist:: list of tuples (id, time), id were the logical addresses.

Thanks for confirmation!

Request: AMDA board clock

The scanner for the 1D-chip is scanning the random neuron everytime, so could you tell me where to provide the external clock from the AMDA board to the 1D scanner? Is there any documentation I am missing here?

MemoryError due to synaptic parameters

Some feature of pyNCS need to be changed to cope with large address spaces due to synaptic parameters (weights, delays etc.). I'm currently dealing with such a system, and address spaces of size 28 bits requires about a gigabyte of memory, which is clearly too much.

One way to address this problem is to remove synaptic parameters (e.g. weights delays) from the input address space, and add them only when they are needed: e.g. when a connection is created. This might involve having a third address Specification (in addition to AER in and AER out), that a new Connection class, say PWConnection, uses to bulid physical addresses corresponding to the right weight,delay, in addition to neuron, synapse, probability, etc.

But this means that even in the absence of a mapper, one requires Connection to stimulate the neurons. We could create a virtual (software) mapper that maps events from the virtual chip to the real neurons.

Any thoughts?

Plotting empty Monitors

Plotting empty monitors currently results in an error. The right behavior should be an empty plot instead.

populate_by_id, populate_by_topology

It would be nice to extend populate_by_topology to more than 2 dimensions. So in 3+ dimensions the rectangle becomes a "cuboid"

I think the documentation in "populate_by_id" does not match what populate_by_id does. I wonder if populate_by_dimension is more adequate. Or am I getting something wrong here?

A "canonical" pyAEX-like module: lab streaming layer

Hi everybody,

Those who has once looked into the pyAex modules remember that it is quite complex and at times difficult to understand and debug.

Recently, there was a talk here by one of the main programmers of BCIlab, a software project used for crunching EEG data. Interestingly, they have a layer called lab streaming layer that interfaces EEG signal acquisition hardware to their Matlab BCIlab interface.

The Lab streaming layer seems to be the perfect tool for replacing the AEX server and client:
http://code.google.com/p/labstreaminglayer/

But also the AMDA board communication.

It is compatible with linux, mac os, and Windows.

I'll try to get a toy ComAPI example soon.

E.

Updating the wiki documentation

Here are the steps for updating the pyNCS documentation:

The documentation is stored using github pages on
http://inincs.github.com/pyNCS/

The sphinx sources are in pyNCS/docs. The build is copied to the gh-pages branch (this is the githubs recommendation)

To update the docs, assuming gh-pages is cloned to pyNCS-docs:
cd pyNCS-docs
rsync pyNCS/docs/_build/html/ ./ -uav
git commit
git push

AEX board configuration for single chip setup

Dear all,
Is there anyway to configure the FPGA on the AEX board to be used for a single-chip setup or the multi-chip setup. Any documentation or the scripts would really be useful.

Thanks,
Harsha

pyNCS headless

I noticed that pyNCS does not load if no display is available. This is annoying if one wants to run through ssh in combination with nohup (I use this for starting a long experiment through ssh and leave at night). This can also be a problem for Mac users connecting to a server through ssh and that have no local X server.

The cause is traitsui.api in ConfAPI.py . The parameter object inherits from hastraits. There seems to be no easy way to cut its dependency to display. Enthought is certainly a nice tool in some cases, but documentation is awful for a package of its size and importance. So I am wondering whether we should get rid of any enthought calls in the core part of pyNCS (i.e. whatever is imported in init).

A workaround for the display problem is to use a virtual frame buffer:
xvfb-run --server-args="-screen 0 1024x768x24" python my_pyncs_script.py

'AddrGroup' has no attribute '_get_dtype'

Related to commit #ffeacba

(Submitted by @aamirsyed )

After the above fix, I just retried the same piece of code - and as it seems, it crashes with another similar error in population.py (pasting partial log once again):

/homes/saamir/.local/lib/python2.7/site-packages/pyNCS/population.pyc in soma2syn(self, addresses, synapses)
306 #return _sort_by_logical(saddrout)
307 somaaddr = addresses
--> 308 dtp = AddrGroup._get_dtype(AddrGroup('',''), self.setup,
309 self.soma.chipid, 'in')
310 syn_dim_names = None

AttributeError: type object 'AddrGroup' has no attribute '_get_dtype'

Error when addrBuildHashTable is called

I'm working with a real-time AER viewer that calls pyST.STas.addrBuildHashTable(stas). On line 1366, addrPhysicalLogicalDecode(stas, addr_phys) is called. However, addr_phys isn't initialized anywhere. My proposed change:

addr_phys = addrLogicalPhysicalDecode(stas, stas.allpos)
addrPhysicalLogicalDecode(stas, addr_phys)

This fixes the error for me. I tried to create a branch and push it to the repo so I could make a pull request, but I don't have permission to access the repo.

Best regards,
Steve

mean rate plot

The function monitor.sl.mean_rate() didnt give expected results for a single neuron population.

NHML AER address specification (PIN layout)

I am trying to figure out how to have the following address specification in the NHML file. The problem is that PIN layout is different for different address ranges as in the attached image.

aer_specification

The address specification in pyNCS has the same pin layout for all the addresses while in my case the same pin layout might have different functions.

can I specify a new address specification? and then, how can I tell the constructor which address specification to use?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.