Giter VIP home page Giter VIP logo

enigma_meg's Introduction

Installation

Setup Conda Environment:

conda install --channel=conda-forge --name=base mamba
mamba create --override-channels --channel=conda-forge --name=enigma_meg mne pip 'python<3.12'
conda activate enigma_meg
pip install git+https://github.com/jstout211/enigma_MEG.git

Description

The programs in this package perform the full processing pipeline for the ENIGMA MEG Working Group. This suite requires that your data be organized in BIDS format; if you need a tool for that you can use enigma_anonymization_lite. The core tool is process_meg.py, which performs all processing steps for the anatomical MRI and the associated MEG. You can either process a single subject, or you can loop over all subjects in batch mode. In order to do batch processing, you must first run parse_bids.py to produce a .csv file manifest of all available MEG scans (or use some other method to generate the .csv file). There are two methods of artifact correction supported. The first is ica with manual identification of ica components. This is likely the most accurate, if you have a very small dataset to process and you have lots of time. The second method is to use ica with MEGnet automated classification of artifact components. MEGnet was retrained on data from the CTF, Elekta/MEGIN, 4D, and KIT data. The model classifies components with >98% accuracy, so this is also an excellent option.

Once all the processing is complete, you can generate QA images using prep_QA.py. Like process_meg.py, prep_QA.py will operate either on a single subject or on all subjects listed in a the .csv file produced by parse_bids.py. Once the .png files are created, you can use Run_enigma_QA_GUI.py to interactively label your subject images as good or bad.

Main Processing Pipeline

usage: process_meg.py [-h] [-bids_root BIDS_ROOT] [-subject SUBJECT] [-subjects_dir SUBJECTS_DIR] [-fs_subject FS_SUBJECT] 
		      [-run RUN] [-session SESSION] [-mains MAINS] [-rest_tag REST_TAG] [-emptyroom_tag EMPTYROOM_TAG] 
		      [-fs_ave_fids] [-proc_fromcsv PROC_FROMCSV] [-n_jobs NJOBS]
		      
This runs all anatomical MRI and MEG processing to produce ENIGMA MEG working gruop results.

optional arguments: 
  -h, --help            show this help message and exit 
  -bids_root BIDS_ROOT 	the root directory for the BIDS tree
  -subject		BIDS ID of the subject to process
  -subjects_dir SUBJECTS_DIR
                        Freesurfer subjects_dir can be assigned at the commandline
                        if not in bids_root/derivatives/freesurfer/subjects
  -fs_subject FSID	Define subject's freesurfer ID if different from BIDS subject ID
  -run RUN		Run identifier for MEG scan in BIDS dir
  -session SESSION	Session identifier for MEG scan in BIDS dir
  -mains MAINS		Powerline frequency, defaults to 60
  -rest_tag REST_TAG	Stem for finding resting state MEG scans in the BIDS directory, defaults to 'rest'
  -emptyroom_tag EMPTYROOM_TAG
  			Stem for finding emptyroom datasets in the BIDS directory, defaults to 'emptyroom'
  -fs_ave_fids		optional coarse registration, please don't use this option unless you have to
  -proc_fromcsv	CSVFILE	Option to loop over rows in a processing manifest .csv file, such as that created
			with parse_bids.py
  -n_jobs NJOBS		Number of workers to use for multithreaded processing
  -ica_manual_qa_prep  	If this flag is present, only those steps up to the ica calculation will be performed,
			at which point you can manually QA the ICA components
  -process_manual_ica_qa
			If this flag is present, resume the analysis after manual identification of ica
			artifactual components
  -do_dics		Perform a DICS analysis instead of LCMV. Not recommended
  -remove_old		Flag to remove all output files and logs from a prior run of process_meg.py

Output:

BIDS_ROOT/derivatives/ENIGMA_MEG/logs/sub-SUBJECT-SESSION_log.txt
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_bem.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_fooof_results_run-01/Band_rel_power.csv
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_fooof_results_run-01/label_spectra.csv
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_run-RUN_lcmv.h5
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_src.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-EMPTYROOM_TAG_run-RUN_cov.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-EMPTYROOM_TAG_run-RUN_epo.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-EMPTYROOM_TAG_run-RUN_proc-filt_meg.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-REST_TAG_run-RUN_cov.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-REST_TAG_run-RUN_epo.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-REST_TAG_run-RUN_fwd.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-REST_TAG_run-RUN_trans.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-REST_TAG_run-RUN_proc-filt_meg.fif
BIDS_ROOT/derivatives/ENIGMA_MEG/sub-SUBJECT/ses-SESSION/meg/sub-SUBJECT_ses-SESSION_task-REST_TAG_run-RUN_ica/
BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJECT/ses-SESSION/sub-SUBJECT_ses-SESSION_tasl=REST_TAG_run-RUN_cleaned.png

Example:

process_meg.py -subjects_dir /data/freesurfer/subjects -bids_root bids_out -subject sub-Subj01 -fs_subject Sub01 -run 01
	-session 1 -mains 50 -rest_tag rest -emptyroom_tag noise -n_jobs 4

This example will run a single subject. This subject has already been processed by freesurfer, and the subjects directory is not in the BIDS tree. In addition, the freesurfer subject ID and BIDS subject ID are different. The rest MEG images are labeled 'task-rest' and the emptyroom datasets are labeled 'task-noise' in the BIDS directory. This data was collected in Europe, so the mains frequency is 50Hz. Process the data collected in session '1' and run '01'. Use 4 workers for any multithreaded functions.

process_meg.py -mains 60 -n_jobs 8 -proc_fromcsv ParsedBIDS_dataframe.csv

In this example, the data were collected in the US where the mains frequency is 60Hz. Use 8 jobs for multithreaded operations. Instead of processing a single subject, process all the subjects in the 'ParsedBIDS_dataframe.csv' file, which was created by parse_bids.py

process_meg.py -subject sub-Subj07 -run 01 -session 1 -mains 60 -ica_manual_qa_prep -remove_old

This example will run sub-Subj07, session 1, run 01, up through the calculation of the ICA, at which point it will stop. Old files from a previous run of this subject will be erased first

process_meg.py -subject sub-Subj07 -run 01 -session 1 -mains 60 -process_manual_ica_qa

This example picks up with processing the same subject after QA of ica components has been performed using Run_enigma_QA_GUI.py (detailed below)

Parsing the BIDS tree for batch processing

usage: parse_bids.py [-h] [-bids_root BIDS_ROOT] [-rest_tag REST_TAG] [-emptyroom_tag EMPTYROOM_TAG]

This python script parses a BIDS directory into a CSV file with one line per MEG to process

options:
  -h, --help            show this help message and exit
  -bids_root BIDS_ROOT  The name of the BIDS directory to be parsed
  -rest_tag REST_TAG    The filename stem to find rest datasets
  -emptyroom_tag EMPTYROOM_TAG
                        The filename stem to find emptyroom datasets

Output:

ParsedBIDS_dataframe.csv

Example:

parse_bids.py -bids_root bids_out -rest_tag resteyesopen -emptyroom_tag emptyroom

In this example, parse the BIDS tree located in 'bids_out'. The rest MEG images are labeled 'task-resteyesopen' and the emptyroom datasets are labeled 'task-emptyroom'. Importantly, this parser will look for MRI files that are potentially stored in a separate session from the MEG data, and will attempt to match. A visual inspection of the .csv file is recommended before using the .csv file for batch processing of process_meg.py

Extract QA images for assessment

usage: enigma_prep_QA.py [-h] [-bids_root BIDS_ROOT] [-subjects_dir SUBJECTS_DIR] [-subjid SUBJID] [-session SESSION] [-run RUN] [-proc_from_csv PROC_FROM_CSV]

This python script will compile a series of QA images for assessment of the enigma_MEG pipeline

options:
  -h, --help            show this help message and exit
  -bids_root BIDS_ROOT  BIDS root directory
  -subjects_dir SUBJECTS_DIR
                        Freesurfer subjects_dir can be assigned at the commandline if not already exported
  -subjid SUBJID        Define the BIDS subject id to process
  -fs_subjid FSID	Freesurfer subject id if different from subjid
  -session SESSION      Session number
  -run RUN              Run number, note that 01 is different from 1
  -proc_from_csv PROC_FROM_CSV
                        Loop over all subjects in a .csv file (ideally produced by parse_bids.py

Output:

BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJID/ses-SESSION/sub-SUBJID_ses-SESSION_run-RUN_beamformer.png
BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJID/ses-SESSION/sub-SUBJID_ses-SESSION_run-RUN_bem.png
BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJID/ses-SESSION/sub-SUBJID_ses-SESSION_run-RUN_coreg.png
BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJID/ses-SESSION/sub-SUBJID_ses-SESSION_run-RUN_spectra.png
BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJID/ses-SESSION/sub-SUBJID_ses-SESSION_run-RUN_src.png
BIDS_ROOT/derivatives/ENIGMA_MEG_QA/sub-SUBJID/ses-SESSION/sub-SUBJID_ses-SESSION_run-RUN_surf.png

Example:

enigma_prep_QA.py -bids_root BIDS -subjid sub-Subj1 -fs_subjid Subj1 -subjects_dir /home/freesurfer/subjects -session 1 -run 01

In this example, the bids directory is called BIDS, and we want to prepare the QA images for subject sub-Sub1. The freesurfer processing was run separately in the directory /home/freesurfer/subjects, where the subject id is Sub1. Process the QA images for session 1 and run01.

Run the QA GUI

usage: Run_enigma_QA_GUI.py [-h] [-bids_root BIDS_ROOT] [-QAtype QATYPE] [-rows ROWS] [-columns COLUMNS] [-imgsize IMGSIZE]

options:
  -h, --help            show this help message and exit
  -bids_root BIDS_ROOT  Location of bids directory, default=bids_out
  -QAtype QATYPE        QA type to run. Options are:
  			'coreg' - produces images showing MEG/MRI alignment
			'bem' - shows the bem surface overlaid on the anatomical MRI
			'surf' - shows the freesurfer source reconstruction from multiple angles, along with the parcellation
			'src' - shows the source space 
			'spectra' - shows the average power spectral density in all sensors (overlaid)
			'beamformer' - shows the source localized (per parcel) alpha power overlaid on the brain surface
			'cleaned' - shows an overlay of raw data before and after ica cleaning
			'ica' - shows the ica time series and topomaps for components to allow manual idenfication of artifacts
	
  -rows ROWS            number of rows in QA browser, default value dependent on QAtype
  -columns COLUMNS      number of columns in QA browser, default value dependent on QAtype
  -imgsize IMGSIZE      make images smaller or larger, default value dependent on QAtype

Output:

BIDS_ROOT/derivatives/ENIGMA_MEG_QA/QATYPE_QA_logfile.txt

Example:

Run_enigma_QA_GUI.py -bids_root BIDS_OUT -QAtype coreg -rows 3 -columns 6 -imgsize 500

In this example, the BIDS root directory is BIDS_OUT, and you want to QA the MRI/MEG coregistrations. You have a very large widescreen monitor, so you can QA a 3x6 array of images at once.

Run_enigma_QA_GUI.py -bids_root BIDS_OUT -QAtype ica

In this example, all of the calculated ica components for all subjects will be loaded into the QA viewer. This option exists for users that do not wish to use the MEGnet automated classifier and instead prefer to identify artifactual components manually. Once you have identified the bad components you can return to run process_meg.py with -process_manual_ica_qa

enigma_meg's People

Contributors

amnamyst avatar jstout211 avatar nugenta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

lychfindel

enigma_meg's Issues

Initialize QA dir upon object creation

If the ENIGMA_MEG_QA is not created - mkdir it.

(enigma_meg_test) jstout@kani:/fast/ENIGMA_OHBM$ ~/src/enigma_MEG/enigmeg/QA/Run_enigma_QA_GUI.py -bids_root $(pwd) -QAtype 'bem'
Traceback (most recent call last):
  File "/home/jstout/src/enigma_MEG/enigmeg/QA/Run_enigma_QA_GUI.py", line 82, in <module>
    history_log = initialize(bids_root, QAtype)        
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jstout/src/enigma_MEG/enigmeg/QA/enigma_QA_GUI_functions.py", line 170, in initialize
    logging.basicConfig(filename=logfile, encoding='utf-8', level=logging.INFO, 
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jstout/miniconda3/envs/enigma_meg_test/lib/python3.11/logging/__init__.py", line 2050, in basicConfig
    h = FileHandler(filename, mode,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jstout/miniconda3/envs/enigma_meg_test/lib/python3.11/logging/__init__.py", line 1181, in __init__
    StreamHandler.__init__(self, self._open())
                                 ^^^^^^^^^^^^
  File "/home/jstout/miniconda3/envs/enigma_meg_test/lib/python3.11/logging/__init__.py", line 1213, in _open
    return open_func(self.baseFilename, self.mode,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/fast/ENIGMA_OHBM/derivatives/ENIGMA_MEG_QA/bem_QA_logfile.txt'

Print Null image if overflow past the image_list length

def create_window_layout(image_list=None, sub_obj_list=None, qa_type=None, grid_size=GRID_SIZE,
                    frame_start_idx=0, resize_xy=(600,600)):
    layout = [[sg.Text(f'QA: {qa_type}')]]
    frame_end_idx=frame_start_idx+grid_size[0]*grid_size[1]
    current_idx = copy.deepcopy(frame_start_idx)
    for i in range(grid_size[0]):
        row = []
        for j in range(grid_size[1]):
            if current_idx > len(image_list):
                print('None Available')    **#!!! FIX - show a null image**

Initalization - Logfile Parse

Read previous logfile
Parse the last time
Readout all of the subject status items into a dictionary
During subject initialization - reference the history dictionary to pull status and initialize button color

ICA cleaned png

Something in here is causing errors that prevent the code from completing

        QAdir = op.join(self.bids_root,'derivatives/ENIGMA_MEG_QA')
        QAsubjdir = QAdir + '/sub-' + self.subject + '/ses-' + self.session
        figname_icaoverlay = QAsubjdir + '/sub-' + self.subject + '_ses-' + self.session + '_run-' + self.run + '_cleaned.png'
        ica=mne.preprocessing.read_ica(op.join(self.fnames.ica))
        ica.exclude = self.ica_comps_toremove #meg_rest_raw.icacomps
        self.load_data()
        fig=ica.plot_overlay(self.raw_rest, exclude=self.ica_comps_toremove)
        fig.savefig(figname_icaoverlay)

Verify single parcellation in group data

In compile_alpha_hist_plots (to be renamed) - provide a parc file from fsaverage. Remove any labels in dataframe that are not part of the current parc (eg. aparc vs aparc_sub).

Precompile base64 images to object tag

There is a lag between "page" turns / next because the images have to be resized and converted to base64.

This should be done before the GUI component is run to prevent lags.

Create QA images using MNE Report

In the final process_meg function:
Write raw-cov.fif
Write eroom-cov.fif
Write -trans.fif
Write downsampled...fif

from mne import Report

#Run the following to collect and write out the QA files
report = Report(image_format='png', subjects_dir=subjects_dir,
subject='APBWVFAR_fs_ortho', info_fname='APBWVFAR_300fs_meg.fif',
raw_psd=False) # use False for speed here

meg_path = '/home/stoutjd/data/DEMO_ENIGMA/outputs/APBWVFAR_fs_ortho'
report.parse_folder(meg_path, on_error='ignore', mri_decim=10)
report.save('outfile.html')

Add functionality to loop over entire BIDS tree

Need to loop over entire tree rather than call for individual subjects. Should this be in a multi processing pool? And there will need to be handling of BIDS trees that have more than one run/session per subject, or meg and anat in different sessions. Write separate function to handle different scenarios.

Tensorflow add ons issue

Remove model dependency on tensorflow addons (currently required because of the f1_score). Tensorflow addons is currently required to load the model. The source code support will be deprecated 2024.

Solution - compile model without f1_score metric or use f1_score metric from another source -- see below announcement. Sci-kit learn also has an f1 score metric.

planned end of life in May 2024. Please modify downstream libraries to take dependencies from other repositories in our TensorFlow community (e.g. Keras, Keras-CV, and Keras-NLP)

For more information see: https://github.com/tensorflow/addons/issues/2807

Make a QA list to query

  • QA list will be empty
  • Upon review - the classification will be unchecked
  • After the next/final button is selected - change to good/bad
  • Write good/bad to QA text file

Make the -subjects_dir and -fs_subject combination actually work

Users can specify a freesurfer directory not in BIDS_ROOT/derivatives/freesurfer/subjects if their subjects have already been processed using FreeSurfer. In this case, it is quite likely that the BIDS subject ID (which is always appended with sub-, as in sub-Sub01) is different from the Freesurfer subject ID (which is most likely Sub01, using the prior example). We should make this combination of options work. At the moment, it doesn't. This would involve changes to both process_meg.py and enigma_prep_QA.py

If no emptyroom is present - compensation grade is still checked

ERROR:23490_ses_None:vendor_prep :: 'NoneType' object has no attribute 'compensation_grade'
Traceback (most recent call last):
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 78, in wrapper
output = function(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 447, in vendor_prep
if self.raw_eroom.compensation_grade != 3:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'compensation_grade'
Traceback (most recent call last):
File "/vf/users/MEGmodules/modules/enigma_meg0.5/bin/process_meg.py", line 7, in
exec(compile(f.read(), file, 'exec'))
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 1403, in
process_subject(args.subject, args) # process the single specified subject
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 1134, in process_subject
proc.do_proc_allsteps()
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 836, in do_proc_allsteps
self.vendor_prep()
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 78, in wrapper
output = function(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/vf/users/MEGmodules/modules/enigma_meg0.5_extras/enigma_MEG/enigmeg/process_meg.py", line 447, in vendor_prep
if self.raw_eroom.compensation_grade != 3:

enigma_QA_prep

Code doesn't seem to work for the CAMCAN or MOUS datasets

Datasets without emptyroom not running properly

ValueError                                Traceback (most recent call last)
Cell In [7], line 1
----> 1 proc.load_data()

File /vf/users/MEGmodules/modules/enigma_meg0.2_extras/enigma_MEG/enigmeg/process_meg.py:188, in process.load_data(self)
    186     self.raw_rest.pick_types(meg=True, eeg=False)
    187 if not hasattr(self, 'raw_eroom'):
--> 188     self.raw_eroom = load_data(self.meg_er_raw.fpath) 
    189     self.raw_eroom.pick_types(meg=True, eeg=False)

File /vf/users/MEGmodules/modules/enigma_meg0.2_extras/enigma_MEG/enigmeg/process_meg.py:553, in load_data(filename)
    552 def load_data(filename):
--> 553     datatype, _ = check_datatype(filename)
    554     dataloader = return_dataloader(datatype)
    555     raw = dataloader(filename, preload=True)

File /vf/users/MEGmodules/modules/enigma_meg0.2_extras/enigma_MEG/enigmeg/process_meg.py:539, in check_datatype(filename)
    537     return 'kit', None
    538 else:
--> 539     raise ValueError('Could not detect datatype')

Rank Calculation for beamformer

    proc.do_proc_allsteps()
  File "/vf/users/MEGmodules/modules/enigma_meg0.2_extras/enigma_MEG/enigmeg/process_meg.py", line 504, in do_proc_allsteps
    self.do_beamformer()
  File "/vf/users/MEGmodules/modules/enigma_meg0.2_extras/enigma_MEG/enigmeg/process_meg.py", line 374, in do_beamformer
    if epo_rank < noise_rank:
TypeError: '<' not supported between instances of 'dict' and 'dict'

Solution - set the rank according to sensor type

            if 'mag' in epo_rank:
                if epo_rank['mag'] < noise_rank['mag']:
                    noise_rank['mag']=epo_rank['mag']
            if 'grad' in epo_rank:
                if epo_rank['grad'] < noise_rank['grad']:
                    noise_rank['grad']=epo_rank['grad']

Remove function from process object

Move the function initialize from csv to outside of the process object. This can just be a function that loads the process object.

def initialize_fromcsv(self, csv_info)

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.