elphyio.py 155 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276
  1. # -*- coding: utf-8 -*-
  2. """
  3. README
  4. =====================================================================================
  5. This is the implementation of the NEO IO for Elphy files.
  6. IO dependencies:
  7. - NEO
  8. - types
  9. - numpy
  10. - quantities
  11. Quick reference:
  12. =====================================================================================
  13. Class ElphyIO() with methods read_block() and write_block() are implemented.
  14. This classes represent the way to access and produce Elphy files
  15. from NEO objects.
  16. As regards reading an existing Elphy file, start by initializing a IO class with it:
  17. >>> import neo
  18. >>> r = neo.io.ElphyIO( filename="Elphy.DAT" )
  19. >>> r
  20. <neo.io.elphyio.ElphyIO object at 0xa1e960c>
  21. Read the file content into NEO object Block:
  22. >>> bl = r.read_block(lazy=False, cascade=True)
  23. >>> bl
  24. <neo.core.block.Block object at 0x9e3d44c>
  25. Now you can then read all Elphy data as NEO objects:
  26. >>> b1.segments
  27. [<neo.core.segment.Segment object at 0x9ed85cc>,
  28. <neo.core.segment.Segment object at 0x9ed85ec>,
  29. <neo.core.segment.Segment object at 0x9ed880c>,
  30. <neo.core.segment.Segment object at 0x9ed89cc>]
  31. >>> bl.segments[0].analogsignals[0]
  32. <AnalogSignal(array([ 0. , -0.0061037 , -0.0061037 , ..., 0. ,
  33. -0.0061037 , -0.01831111]) * mV, [0.0 s, 7226.2 s], sampling rate: 10.0 Hz)>
  34. These functions return NEO objects, completely "detached" from the original Elphy file.
  35. Changes to the runtime objects will not cause any changes in the file.
  36. Having already existing NEO structures, it is possible to write them as an Elphy file.
  37. For example, given a segment:
  38. >>> s = neo.Segment()
  39. filled with other NEO structures:
  40. >>> import numpy as np
  41. >>> import quantities as pq
  42. >>> a = AnalogSignal( signal=np.random.rand(300), t_start=42*pq.ms)
  43. >>> s.analogsignals.append( a )
  44. and added to a newly created NEO Block:
  45. >>> bl = neo.Block()
  46. >>> bl.segments.append( s )
  47. Then, it's easy to create an Elphy file:
  48. >>> r = neo.io.ElphyIO( filename="ElphyNeoTest.DAT" )
  49. >>> r.write_block( bl )
  50. Author: Thierry Brizzi
  51. Domenico Guarino
  52. """
  53. # needed for python 3 compatibility
  54. from __future__ import absolute_import
  55. # python commons:
  56. from datetime import datetime
  57. from fractions import gcd
  58. from os import path
  59. import re
  60. import struct
  61. from time import time
  62. # note neo.core needs only numpy and quantities
  63. import numpy as np
  64. import quantities as pq
  65. # I need to subclass BaseIO
  66. from neo.io.baseio import BaseIO
  67. # to import from core
  68. from neo.core import (Block, Segment, ChannelIndex, RecordingChannel,
  69. AnalogSignal, Event, SpikeTrain)
  70. # --------------------------------------------------------
  71. # OBJECTS
  72. class ElphyScaleFactor(object):
  73. """
  74. Useful to retrieve real values from integer
  75. ones that are stored in an Elphy file :
  76. ``scale`` : compute the actual value of a sample
  77. with this following formula :
  78. ``delta`` * value + ``offset``
  79. """
  80. def __init__(self, delta, offset):
  81. self.delta = delta
  82. self.offset = offset
  83. def scale(self, value):
  84. return value * self.delta + self.offset
  85. class BaseSignal(object):
  86. """
  87. A descriptor storing main signal properties :
  88. ``layout`` : the :class:``ElphyLayout` object
  89. that extracts data from a file.
  90. ``episode`` : the episode in which the signal
  91. has been acquired.
  92. ``sampling_frequency`` : the sampling frequency
  93. of the analog to digital converter.
  94. ``sampling_period`` : the sampling period of the
  95. analog to digital converter computed from sampling_frequency.
  96. ``t_start`` : the start time of the signal acquisition.
  97. ``t_stop`` : the end time of the signal acquisition.
  98. ``duration`` : the duration of the signal acquisition
  99. computed from t_start and t_stop.
  100. ``n_samples`` : the number of sample acquired during the
  101. recording computed from the duration and the sampling period.
  102. ``name`` : a label to identify the signal.
  103. ``data`` : a property triggering data extraction.
  104. """
  105. def __init__(self, layout, episode, sampling_frequency, start, stop, name=None):
  106. self.layout = layout
  107. self.episode = episode
  108. self.sampling_frequency = sampling_frequency
  109. self.sampling_period = 1 / sampling_frequency
  110. self.t_start = start
  111. self.t_stop = stop
  112. self.duration = self.t_stop - self.t_start
  113. self.n_samples = int(self.duration / self.sampling_period)
  114. self.name = name
  115. @property
  116. def data(self):
  117. raise NotImplementedError('must be overloaded in subclass')
  118. class ElphySignal(BaseSignal):
  119. """
  120. Subclass of :class:`BaseSignal` corresponding to Elphy's analog channels :
  121. ``channel`` : the identifier of the analog channel providing the signal.
  122. ``units`` : an array containing x and y coordinates units.
  123. ``x_unit`` : a property to access the x-coordinates unit.
  124. ``y_unit`` : a property to access the y-coordinates unit.
  125. ``data`` : a property that delegate data extraction to the
  126. ``get_signal_data`` function of the ```layout`` object.
  127. """
  128. def __init__(self, layout, episode, channel, x_unit, y_unit, sampling_frequency, start, stop, name=None):
  129. super(ElphySignal, self).__init__(layout, episode, sampling_frequency, start, stop, name)
  130. self.channel = channel
  131. self.units = [x_unit, y_unit]
  132. def __str__(self):
  133. return "%s ep_%s ch_%s [%s, %s]" % (self.layout.file.name, self.episode, self.channel, self.x_unit, self.y_unit)
  134. def __repr__(self):
  135. return self.__str__()
  136. @property
  137. def x_unit(self):
  138. """
  139. Return the x-coordinate of the signal.
  140. """
  141. return self.units[0]
  142. @property
  143. def y_unit(self):
  144. """
  145. Return the y-coordinate of the signal.
  146. """
  147. return self.units[1]
  148. @property
  149. def data(self):
  150. return self.layout.get_signal_data(self.episode, self.channel)
  151. class ElphyTag(BaseSignal):
  152. """
  153. Subclass of :class:`BaseSignal` corresponding to Elphy's tag channels :
  154. ``number`` : the identifier of the tag channel.
  155. ``x_unit`` : the unit of the x-coordinate.
  156. """
  157. def __init__(self, layout, episode, number, x_unit, sampling_frequency, start, stop, name=None):
  158. super(ElphyTag, self).__init__(layout, episode, sampling_frequency, start, stop, name)
  159. self.number = number
  160. self.units = [x_unit, None]
  161. def __str__(self):
  162. return "%s : ep_%s tag_ch_%s [%s]" % (self.layout.file.name, self.episode, self.number, self.x_unit)
  163. def __repr__(self):
  164. return self.__str__()
  165. @property
  166. def x_unit(self):
  167. """
  168. Return the x-coordinate of the signal.
  169. """
  170. return self.units[0]
  171. @property
  172. def data(self):
  173. return self.layout.get_tag_data(self.episode, self.number)
  174. @property
  175. def channel(self):
  176. return self.number
  177. class ElphyEvent(object):
  178. """
  179. A descriptor that store a set of events properties :
  180. ``layout`` : the :class:``ElphyLayout` object
  181. that extracts data from a file.
  182. ``episode`` : the episode in which the signal
  183. has been acquired.
  184. ``number`` : the identifier of the channel.
  185. ``x_unit`` : the unit of the x-coordinate.
  186. ``n_events`` : the number of events.
  187. ``name`` : a label to identify the event.
  188. ``times`` : a property triggering event times extraction.
  189. """
  190. def __init__(self, layout, episode, number, x_unit, n_events, ch_number=None, name=None):
  191. self.layout = layout
  192. self.episode = episode
  193. self.number = number
  194. self.x_unit = x_unit
  195. self.n_events = n_events
  196. self.name = name
  197. self.ch_number = ch_number
  198. def __str__(self):
  199. return "%s : ep_%s evt_ch_%s [%s]" % (self.layout.file.name, self.episode, self.number, self.x_unit)
  200. def __repr__(self):
  201. return self.__str__()
  202. @property
  203. def channel(self):
  204. return self.number
  205. @property
  206. def times(self):
  207. return self.layout.get_event_data(self.episode, self.number)
  208. @property
  209. def data(self):
  210. return self.times
  211. class ElphySpikeTrain(ElphyEvent):
  212. """
  213. A descriptor that store spiketrain properties :
  214. ``wf_samples`` : number of samples composing waveforms.
  215. ``wf_sampling_frequency`` : sampling frequency of waveforms.
  216. ``wf_sampling_period`` : sampling period of waveforms.
  217. ``wf_units`` : the units of the x and y coordinates of waveforms.
  218. ``t_start`` : the time before the arrival of the spike which
  219. corresponds to the starting time of a waveform.
  220. ``name`` : a label to identify the event.
  221. ``times`` : a property triggering event times extraction.
  222. ``waveforms`` : a property triggering waveforms extraction.
  223. """
  224. def __init__(self, layout, episode, number, x_unit, n_events, wf_sampling_frequency, wf_samples, unit_x_wf, unit_y_wf, t_start, name=None):
  225. super(ElphySpikeTrain, self).__init__(layout, episode, number, x_unit, n_events, name)
  226. self.wf_samples = wf_samples
  227. self.wf_sampling_frequency = wf_sampling_frequency
  228. assert wf_sampling_frequency, "bad sampling frequency"
  229. self.wf_sampling_period = 1.0 / wf_sampling_frequency
  230. self.wf_units = [unit_x_wf, unit_y_wf]
  231. self.t_start = t_start
  232. @property
  233. def x_unit_wf(self):
  234. """
  235. Return the x-coordinate of waveforms.
  236. """
  237. return self.wf_units[0]
  238. @property
  239. def y_unit_wf(self):
  240. """
  241. Return the y-coordinate of waveforms.
  242. """
  243. return self.wf_units[1]
  244. @property
  245. def times(self):
  246. return self.layout.get_spiketrain_data(self.episode, self.number)
  247. @property
  248. def waveforms(self):
  249. return self.layout.get_waveform_data(self.episode, self.number) if self.wf_samples else None
  250. # --------------------------------------------------------
  251. # BLOCKS
  252. class BaseBlock(object):
  253. """
  254. Represent a chunk of file storing metadata or
  255. raw data. A convenient class to break down the
  256. structure of an Elphy file to several building
  257. blocks :
  258. ``layout`` : the layout containing the block.
  259. ``identifier`` : the label that identified the block.
  260. ``size`` : the size of the block.
  261. ``start`` : the file index corresponding to the starting byte of the block.
  262. ``end`` : the file index corresponding to the ending byte of the block
  263. NB : Subclassing this class is a convenient
  264. way to set the properties using polymorphism
  265. rather than a conditional structure. By this
  266. way each :class:`BaseBlock` type know how to
  267. iterate through the Elphy file and store
  268. interesting data.
  269. """
  270. def __init__(self, layout, identifier, start, size):
  271. self.layout = layout
  272. self.identifier = identifier
  273. self.size = size
  274. self.start = start
  275. self.end = self.start + self.size - 1
  276. class ElphyBlock(BaseBlock):
  277. """
  278. A subclass of :class:`BaseBlock`. Useful to
  279. store the location and size of interesting
  280. data within a block :
  281. ``parent_block`` : the parent block containing the block.
  282. ``header_size`` : the size of the header permitting the
  283. identification of the type of the block.
  284. ``data_offset`` : the file index located after the block header.
  285. ``data_size`` : the size of data located after the header.
  286. ``sub_blocks`` : the sub-blocks contained by the block.
  287. """
  288. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="i", parent_block=None):
  289. super(ElphyBlock, self).__init__(layout, identifier, start, size)
  290. # a block may be a sub-block of another block
  291. self.parent_block = parent_block
  292. # pascal language store strings in 2 different ways
  293. # ... first, if in the program the size of the string is
  294. # specified (fixed) then the file stores the length
  295. # of the string and allocate a number of bytes equal
  296. # to the specified size
  297. # ... if this size is not specified the length of the
  298. # string is also stored but the file allocate dynamically
  299. # a number of bytes equal to the actual size of the string
  300. l_ident = len(self.identifier)
  301. if fixed_length :
  302. l_ident += (fixed_length - l_ident)
  303. self.header_size = l_ident + 1 + type_dict[size_format]
  304. # starting point of data located in the block
  305. self.data_offset = self.start + self.header_size
  306. self.data_size = self.size - self.header_size
  307. # a block may have sub-blocks
  308. # it is to subclasses to initialize
  309. # this property
  310. self.sub_blocks = list()
  311. def __repr__(self):
  312. return "%s : size = %s, start = %s, end = %s" % (self.identifier, self.size, self.start, self.end)
  313. def add_sub_block(self, block):
  314. """
  315. Append a block to the sub-block list.
  316. """
  317. self.sub_blocks.append(block)
  318. class FileInfoBlock(ElphyBlock):
  319. """
  320. Base class of all subclasses whose the purpose is to
  321. extract user file info stored into an Elphy file :
  322. ``header`` : the header block relative to the block.
  323. ``file`` : the file containing the block.
  324. NB : User defined metadata are not really practical.
  325. An Elphy script must know the order of metadata storage
  326. to know exactly how to retrieve these data. That's why
  327. it is necessary to subclass and reproduce elphy script
  328. commands to extract metadata relative to a protocol.
  329. Consequently managing a new protocol implies to refactor
  330. the file info extraction.
  331. """
  332. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="i", parent_block=None):
  333. super(FileInfoBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format, parent_block=parent_block)
  334. self.header = None
  335. self.file = self.layout.file
  336. def get_protocol_and_version(self):
  337. """
  338. Return a tuple useful to identify the
  339. kind of protocol that has generated a
  340. file during data acquisition.
  341. """
  342. raise Exception("must be overloaded in a subclass")
  343. def get_user_file_info(self):
  344. """
  345. Return a dictionary containing all
  346. user file info stored in the file.
  347. """
  348. raise Exception("must be overloaded in a subclass")
  349. def get_sparsenoise_revcor(self):
  350. """
  351. Return 'REVCOR' user file info. This method is common
  352. to :class:`ClassicFileInfo` and :class:`MultistimFileInfo`
  353. because the last one is able to store this kind of metadata.
  354. """
  355. header = dict()
  356. header['n_div_x'] = read_from_char(self.file, 'h')
  357. header['n_div_y'] = read_from_char(self.file, 'h')
  358. header['gray_levels'] = read_from_char(self.file, 'h')
  359. header['position_x'] = read_from_char(self.file, 'ext')
  360. header['position_y'] = read_from_char(self.file, 'ext')
  361. header['length'] = read_from_char(self.file, 'ext')
  362. header['width'] = read_from_char(self.file, 'ext')
  363. header['orientation'] = read_from_char(self.file, 'ext')
  364. header['expansion'] = read_from_char(self.file, 'h')
  365. header['scotoma'] = read_from_char(self.file, 'h')
  366. header['seed'] = read_from_char(self.file, 'h')
  367. #dt_on and dt_off may not exist in old revcor formats
  368. rollback = self.file.tell()
  369. header['dt_on'] = read_from_char(self.file, 'ext')
  370. if header['dt_on'] is None :
  371. self.file.seek(rollback)
  372. rollback = self.file.tell()
  373. header['dt_off'] = read_from_char(self.file, 'ext')
  374. if header['dt_off'] is None :
  375. self.file.seek(rollback)
  376. return header
  377. class ClassicFileInfo(FileInfoBlock):
  378. """
  379. Extract user file info stored into an Elphy file corresponding to
  380. sparse noise (revcor), moving bar and flashbar protocols.
  381. """
  382. def detect_protocol_from_name(self, path):
  383. pattern = "\d{4}(\d+|\D)\D"
  384. codes = {
  385. 'r':'sparsenoise',
  386. 'o':'movingbar',
  387. 'f':'flashbar',
  388. 'm':'multistim' # here just for assertion
  389. }
  390. filename = path.split(path)[1]
  391. match = re.search(pattern, path)
  392. if hasattr(match, 'end') :
  393. code = codes.get(path[match.end() - 1].lower(), None)
  394. assert code != 'm', "multistim file detected"
  395. return code
  396. elif 'spt' in filename.lower() :
  397. return 'spontaneousactivity'
  398. else :
  399. return None
  400. def get_protocol_and_version(self):
  401. if self.layout and self.layout.info_block :
  402. self.file.seek(self.layout.info_block.data_offset)
  403. version = self.get_title()
  404. if version in ['REVCOR1', 'REVCOR2', 'REVCOR + PAIRING'] :
  405. name = "sparsenoise"
  406. elif version in ['BARFLASH'] :
  407. name = "flashbar"
  408. elif version in ['ORISTIM', 'ORISTM', 'ORISTM1', 'ORITUN'] :
  409. name = "movingbar"
  410. else :
  411. name = self.detect_protocol_from_name(self.file.name)
  412. self.file.seek(0)
  413. return name, version
  414. return None, None
  415. def get_title(self):
  416. title_length, title = struct.unpack('<B20s', self.file.read(21))
  417. return unicode(title[0:title_length])
  418. def get_user_file_info(self):
  419. header = dict()
  420. if self.layout and self.layout.info_block :
  421. self.file.seek(self.layout.info_block.data_offset)
  422. header['title'] = self.get_title()
  423. # test the protocol name to trigger
  424. # the right header extraction
  425. if self.layout.elphy_file.protocol == 'sparsenoise' :
  426. header.update(self.get_sparsenoise_revcor())
  427. elif self.layout.elphy_file.protocol == 'flashbar' :
  428. header.update(self.get_flashbar_header())
  429. elif self.layout.elphy_file.protocol == 'movingbar' :
  430. header.update(self.get_movingbar_header())
  431. self.file.seek(0)
  432. return header
  433. def get_flashbar_header(self):
  434. header = dict()
  435. orientations = list()
  436. tmp = self.file.tell()
  437. for _ in range(0, 50) :
  438. l, ori = struct.unpack('<B5s', self.file.read(6))
  439. try :
  440. orientations.append(float(ori[0:l]))
  441. except :
  442. return header
  443. header['orientations'] = orientations if orientations else None
  444. self.file.seek(tmp + 50 * 6)
  445. _tmp = read_from_char(self.file, 'h')
  446. header['number_of_orientations'] = _tmp if tmp < 0 else None
  447. _tmp = read_from_char(self.file, 'h')
  448. header['number_of_repetitions'] = _tmp if tmp < 0 else None
  449. header['position_x'] = read_from_char(self.file, 'ext')
  450. header['position_y'] = read_from_char(self.file, 'ext')
  451. header['length'] = read_from_char(self.file, 'ext')
  452. header['width'] = read_from_char(self.file, 'ext')
  453. header['orientation'] = read_from_char(self.file, 'ext')
  454. header['excursion'] = read_from_char(self.file, 'i')
  455. header['dt_on'] = None
  456. return header
  457. def get_movingbar_header(self):
  458. header = dict()
  459. orientations = list()
  460. tmp = self.file.tell()
  461. for _ in range(0, 50) :
  462. l, ori = struct.unpack('<B5s', self.file.read(6))
  463. orientations.append(float(ori[0:l]))
  464. header['orientations'] = orientations if orientations else None
  465. self.file.seek(tmp + 50 * 6)
  466. _tmp = read_from_char(self.file, 'h')
  467. header['number_of_orientations'] = _tmp if tmp < 0 else None
  468. _tmp = read_from_char(self.file, 'h')
  469. header['number_of_repetitions'] = _tmp if tmp < 0 else None
  470. header['position_x'] = read_from_char(self.file, 'ext')
  471. header['position_y'] = read_from_char(self.file, 'ext')
  472. header['length'] = read_from_char(self.file, 'ext')
  473. header['width'] = read_from_char(self.file, 'ext')
  474. header['orientation'] = read_from_char(self.file, 'ext')
  475. header['excursion'] = read_from_char(self.file, 'h')
  476. header['speed'] = read_from_char(self.file, 'h')
  477. header['dim_x'] = read_from_char(self.file, 'h')
  478. header['dim_y'] = read_from_char(self.file, 'h')
  479. return header
  480. class MultistimFileInfo(FileInfoBlock):
  481. def get_protocol_and_version(self):
  482. # test if there is an available info_block
  483. if self.layout and self.layout.info_block :
  484. # go to the info_block
  485. sub_block = self.layout.info_block
  486. self.file.seek(sub_block.data_offset)
  487. #get the first four parameters
  488. #acqLGN = read_from_char(self.file, 'i')
  489. center = read_from_char(self.file, 'i')
  490. surround = read_from_char(self.file, 'i')
  491. version = self.get_title()
  492. # test the type of protocol from
  493. # center and surround parameters
  494. if (surround >= 2) :
  495. name = None
  496. version = None
  497. else :
  498. if center == 2 :
  499. name = "sparsenoise"
  500. elif center == 3 :
  501. name = "densenoise"
  502. elif center == 4 :
  503. name = "densenoise"
  504. elif center == 5 :
  505. name = "grating"
  506. else :
  507. name = None
  508. version = None
  509. self.file.seek(0)
  510. return name, version
  511. return None, None
  512. def get_title(self):
  513. title_length = read_from_char(self.file, 'B')
  514. title, = struct.unpack('<%ss' % title_length, self.file.read(title_length))
  515. self.file.seek(self.file.tell() + 255 - title_length)
  516. return unicode(title)
  517. def get_user_file_info(self):
  518. header = dict()
  519. if self.layout and self.layout.info_block :
  520. # go to the info_block
  521. sub_block = self.layout.info_block
  522. self.file.seek(sub_block.data_offset)
  523. #get the first four parameters
  524. acqLGN = read_from_char(self.file, 'i')
  525. center = read_from_char(self.file, 'i')
  526. surround = read_from_char(self.file, 'i')
  527. #store info in the header
  528. header['acqLGN'] = acqLGN
  529. header['center'] = center
  530. header['surround'] = surround
  531. if not (header['surround'] >= 2) :
  532. header.update(self.get_center_header(center))
  533. self.file.seek(0)
  534. return header
  535. def get_center_header(self, code):
  536. #get file info corresponding
  537. #to the executed protocol
  538. #for the center first ...
  539. if code == 0 :
  540. return self.get_sparsenoise_revcor()
  541. elif code == 2 :
  542. return self.get_sparsenoise_center()
  543. elif code == 3 :
  544. return self.get_densenoise_center(True)
  545. elif code == 4 :
  546. return self.get_densenoise_center(False)
  547. elif code == 5 :
  548. return dict()
  549. # return self.get_grating_center()
  550. else :
  551. return dict()
  552. def get_surround_header(self, code):
  553. #then the surround
  554. if code == 2 :
  555. return self.get_sparsenoise_surround()
  556. elif code == 3 :
  557. return self.get_densenoise_surround(True)
  558. elif code == 4 :
  559. return self.get_densenoise_surround(False)
  560. elif code == 5 :
  561. raise NotImplementedError()
  562. return self.get_grating_center()
  563. else :
  564. return dict()
  565. def get_center_surround(self, center, surround):
  566. header = dict()
  567. header['stim_center'] = self.get_center_header(center)
  568. header['stim_surround'] = self.get_surround_header(surround)
  569. return header
  570. def get_sparsenoise_center(self):
  571. header = dict()
  572. header['title'] = self.get_title()
  573. header['number_of_sequences'] = read_from_char(self.file, 'i')
  574. header['pretrigger_duration'] = read_from_char(self.file, 'ext')
  575. header['n_div_x'] = read_from_char(self.file, 'h')
  576. header['n_div_y'] = read_from_char(self.file, 'h')
  577. header['gray_levels'] = read_from_char(self.file, 'h')
  578. header['position_x'] = read_from_char(self.file, 'ext')
  579. header['position_y'] = read_from_char(self.file, 'ext')
  580. header['length'] = read_from_char(self.file, 'ext')
  581. header['width'] = read_from_char(self.file, 'ext')
  582. header['orientation'] = read_from_char(self.file, 'ext')
  583. header['expansion'] = read_from_char(self.file, 'h')
  584. header['scotoma'] = read_from_char(self.file, 'h')
  585. header['seed'] = read_from_char(self.file, 'h')
  586. header['luminance_1'] = read_from_char(self.file, 'ext')
  587. header['luminance_2'] = read_from_char(self.file, 'ext')
  588. header['dt_count'] = read_from_char(self.file, 'i')
  589. dt_array = list()
  590. for _ in range(0, header['dt_count']) :
  591. dt_array.append(read_from_char(self.file, 'ext'))
  592. header['dt_on'] = dt_array if dt_array else None
  593. header['dt_off'] = read_from_char(self.file, 'ext')
  594. return header
  595. def get_sparsenoise_surround(self):
  596. header = dict()
  597. header['title_surround'] = self.get_title()
  598. header['gap'] = read_from_char(self.file, 'ext')
  599. header['n_div_x'] = read_from_char(self.file, 'h')
  600. header['n_div_y'] = read_from_char(self.file, 'h')
  601. header['gray_levels'] = read_from_char(self.file, 'h')
  602. header['expansion'] = read_from_char(self.file, 'h')
  603. header['scotoma'] = read_from_char(self.file, 'h')
  604. header['seed'] = read_from_char(self.file, 'h')
  605. header['luminance_1'] = read_from_char(self.file, 'ext')
  606. header['luminance_2'] = read_from_char(self.file, 'ext')
  607. header['dt_on'] = read_from_char(self.file, 'ext')
  608. header['dt_off'] = read_from_char(self.file, 'ext')
  609. return header
  610. def get_densenoise_center(self, is_binary):
  611. header = dict()
  612. header['stimulus_type'] = "B" if is_binary else "T"
  613. header['title'] = self.get_title()
  614. _tmp = read_from_char(self.file, 'i')
  615. header['number_of_sequences'] = _tmp if _tmp < 0 else None
  616. rollback = self.file.tell()
  617. header['stimulus_duration'] = read_from_char(self.file, 'ext')
  618. if header['stimulus_duration'] is None :
  619. self.file.seek(rollback)
  620. header['pretrigger_duration'] = read_from_char(self.file, 'ext')
  621. header['n_div_x'] = read_from_char(self.file, 'h')
  622. header['n_div_y'] = read_from_char(self.file, 'h')
  623. header['position_x'] = read_from_char(self.file, 'ext')
  624. header['position_y'] = read_from_char(self.file, 'ext')
  625. header['length'] = read_from_char(self.file, 'ext')
  626. header['width'] = read_from_char(self.file, 'ext')
  627. header['orientation'] = read_from_char(self.file, 'ext')
  628. header['expansion'] = read_from_char(self.file, 'h')
  629. header['seed'] = read_from_char(self.file, 'h')
  630. header['luminance_1'] = read_from_char(self.file, 'ext')
  631. header['luminance_2'] = read_from_char(self.file, 'ext')
  632. header['dt_on'] = read_from_char(self.file, 'ext')
  633. header['dt_off'] = read_from_char(self.file, 'ext')
  634. return header
  635. def get_densenoise_surround(self, is_binary):
  636. header = dict()
  637. header['title_surround'] = self.get_title()
  638. header['gap'] = read_from_char(self.file, 'ext')
  639. header['n_div_x'] = read_from_char(self.file, 'h')
  640. header['n_div_y'] = read_from_char(self.file, 'h')
  641. header['expansion'] = read_from_char(self.file, 'h')
  642. header['seed'] = read_from_char(self.file, 'h')
  643. header['luminance_1'] = read_from_char(self.file, 'ext')
  644. header['luminance_2'] = read_from_char(self.file, 'ext')
  645. header['dt_on'] = read_from_char(self.file, 'ext')
  646. header['dt_off'] = read_from_char(self.file, 'ext')
  647. return header
  648. def get_grating_center(self):
  649. pass
  650. def get_grating_surround(self):
  651. pass
  652. class Header(ElphyBlock):
  653. """
  654. A convenient subclass of :class:`Block` to store
  655. Elphy file header properties.
  656. NB : Subclassing this class is a convenient
  657. way to set the properties of the header using
  658. polymorphism rather than a conditional structure.
  659. """
  660. def __init__(self, layout, identifier, size, fixed_length=None, size_format="i"):
  661. super(Header, self).__init__(layout, identifier, 0, size, fixed_length, size_format)
  662. class Acquis1Header(Header):
  663. """
  664. A subclass of :class:`Header` used to
  665. identify the 'ACQUIS1/GS/1991' format.
  666. Whereas more recent format, the header
  667. contains all data relative to episodes,
  668. channels and traces :
  669. ``n_channels`` : the number of acquisition channels.
  670. ``nbpt`` and ``nbptEx`` : parameters useful to compute the number of samples by episodes.
  671. ``tpData`` : the data format identifier used to compute sample size.
  672. ``x_unit`` : the x-coordinate unit for all channels in an episode.
  673. ``y_units`` : an array containing y-coordinate units for each channel in the episode.
  674. ``dX`` and ``X0`` : the scale factors necessary to retrieve the actual
  675. times relative to each sample in a channel.
  676. ``dY_ar`` and ``Y0_ar``: arrays of scale factors necessary to retrieve
  677. the actual values relative to samples.
  678. ``continuous`` : a boolean telling if the file has been acquired in
  679. continuous mode.
  680. ``preSeqI`` : the size in bytes of the data preceding raw data.
  681. ``postSeqI`` : the size in bytes of the data preceding raw data.
  682. ``dat_length`` : the length in bytes of the data in the file.
  683. ``sample_size`` : the size in bytes of a sample.
  684. ``n_samples`` : the number of samples.
  685. ``ep_size`` : the size in bytes of an episode.
  686. ``n_episodes`` : the number of recording sequences store in the file.
  687. NB :
  688. The size is read from the file,
  689. the identifier is a string containing
  690. 15 characters and the size is encoded
  691. as small integer.
  692. See file 'FicDefAc1.pas' to identify
  693. the parsed parameters.
  694. """
  695. def __init__(self, layout):
  696. fileobj = layout.file
  697. super(Acquis1Header, self).__init__(layout, "ACQUIS1/GS/1991", 1024, 15, "h")
  698. #parse the header to store interesting data about episodes and channels
  699. fileobj.seek(18)
  700. #extract episode properties
  701. n_channels = read_from_char(fileobj, 'B')
  702. assert not ((n_channels < 1) or (n_channels > 16)), "bad number of channels"
  703. nbpt = read_from_char(fileobj, 'h')
  704. l_xu, x_unit = struct.unpack('<B3s', fileobj.read(4))
  705. #extract units for each channel
  706. y_units = list()
  707. for i in range(1, 7) :
  708. l_yu, y_unit = struct.unpack('<B3s', fileobj.read(4))
  709. y_units.append(y_unit[0:l_yu])
  710. #extract i1, i2, x1, x2 and compute dX and X0
  711. i1, i2 = struct.unpack('<hh', fileobj.read(4))
  712. x1 = read_from_char(fileobj, 'ext')
  713. x2 = read_from_char(fileobj, 'ext')
  714. if (i1 != i2) and (x1 != x2) :
  715. dX = (x2 - x1) / (i2 - i1)
  716. X0 = x1 - i1 * dX
  717. else :
  718. dX = None
  719. X0 = None
  720. # raise Exception("bad X-scale parameters")
  721. #extract j1 and j2, y1 and y2 and compute dY
  722. j1 = struct.unpack('<hhhhhh', fileobj.read(12))
  723. j2 = struct.unpack('<hhhhhh', fileobj.read(12))
  724. y1 = list()
  725. for i in range(1, 7) :
  726. y1.append(read_from_char(fileobj, 'ext'))
  727. y2 = list()
  728. for i in range(1, 7) :
  729. y2.append(read_from_char(fileobj, 'ext'))
  730. dY_ar = list()
  731. Y0_ar = list()
  732. for i in range(0, n_channels) :
  733. # detect division by zero
  734. if (j1[i] != j2[i]) and (y1[i] != y2[i]) :
  735. dY_ar.append((y2[i] - y1[i]) / (j2[i] - j1[i]))
  736. Y0_ar.append(y1[i] - j1[i] * dY_ar[i])
  737. else :
  738. dY_ar.append(None)
  739. Y0_ar.append(None)
  740. NbMacq = read_from_char(fileobj, 'h')
  741. #fileobj.read(300) #Macq:typeTabMarqueAcq; { 300 octets }
  742. max_mark = 100
  743. Macq = list()
  744. for i in range(0, max_mark) :
  745. Macq.append(list(struct.unpack('<ch', fileobj.read(3))))
  746. #Xmini,Xmaxi,Ymini,Ymaxi:array[1..6] of float; #fileobj.read(240)
  747. x_mini = list()
  748. for i in range(0, 6) :
  749. x_mini.append(read_from_char(fileobj, 'ext'))
  750. x_maxi = list()
  751. for i in range(0, 6) :
  752. x_maxi.append(read_from_char(fileobj, 'ext'))
  753. y_mini = list()
  754. for i in range(0, 6) :
  755. y_mini.append(read_from_char(fileobj, 'ext'))
  756. y_maxi = list()
  757. for i in range(0, 6) :
  758. y_maxi.append(read_from_char(fileobj, 'ext'))
  759. #modeA:array[1..6] of byte; #fileobj.read(6)
  760. modeA = list(struct.unpack('<BBBBBB', fileobj.read(6)))
  761. continuous = read_from_char(fileobj, '?')
  762. preSeqI, postSeqI = struct.unpack('<hh', fileobj.read(4))
  763. #EchelleSeqI:boolean; #fileobj.read(1)
  764. ep_scaled = read_from_char(fileobj, '?')
  765. nbptEx = read_from_char(fileobj, 'H')
  766. x1s, x2s = struct.unpack('<ff', fileobj.read(8))
  767. y1s = list()
  768. for i in range(0, 6):
  769. y1s.append(read_from_char(fileobj, 'f'))
  770. y2s = list()
  771. for i in range(0, 6):
  772. y2s.append(read_from_char(fileobj, 'f'))
  773. #fileobj.read(96) # Xminis,Xmaxis,Yminis,Ymaxis:array[1..6] of single;
  774. x_minis = list()
  775. for i in range(0, 6) :
  776. x_minis.append(read_from_char(fileobj, 'f'))
  777. x_maxis = list()
  778. for i in range(0, 6) :
  779. x_maxis.append(read_from_char(fileobj, 'f'))
  780. y_minis = list()
  781. for i in range(0, 6) :
  782. y_minis.append(read_from_char(fileobj, 'f'))
  783. y_maxis = list()
  784. for i in range(0, 6) :
  785. y_maxis.append(read_from_char(fileobj, 'f'))
  786. n_ep = read_from_char(fileobj, 'h')
  787. tpData = read_from_char(fileobj, 'h')
  788. assert tpData in [3, 2, 1, 0], "bad sample size"
  789. no_analog_data = read_from_char(fileobj, '?')
  790. self.n_ep = n_ep
  791. self.n_channels = n_channels
  792. self.nbpt = nbpt
  793. self.i1 = i1
  794. self.i2 = i2
  795. self.x1 = x1
  796. self.x2 = x2
  797. self.dX = dX
  798. self.X0 = X0
  799. self.x_unit = x_unit[0:l_xu]
  800. self.dY_ar = dY_ar
  801. self.Y0_ar = Y0_ar
  802. self.y_units = y_units[0:n_channels]
  803. self.NbMacq = NbMacq
  804. self.Macq = Macq
  805. self.x_mini = x_mini[0:n_channels]
  806. self.x_maxi = x_maxi[0:n_channels]
  807. self.y_mini = y_mini[0:n_channels]
  808. self.y_maxi = y_maxi[0:n_channels]
  809. self.modeA = modeA
  810. self.continuous = continuous
  811. self.preSeqI = preSeqI
  812. self.postSeqI = postSeqI
  813. self.ep_scaled = ep_scaled
  814. self.nbptEx = nbptEx
  815. self.x1s = x1s
  816. self.x2s = x2s
  817. self.y1s = y1s
  818. self.y2s = y2s
  819. self.x_minis = x_minis[0:n_channels]
  820. self.x_maxis = x_maxis[0:n_channels]
  821. self.y_minis = y_minis[0:n_channels]
  822. self.y_maxis = y_maxis[0:n_channels]
  823. self.tpData = 2 if not tpData else tpData
  824. self.no_analog_data = no_analog_data
  825. self.dat_length = self.layout.file_size - self.layout.data_offset
  826. self.sample_size = type_dict[types[tpData]]
  827. if self.continuous :
  828. self.n_samples = self.dat_length / (self.n_channels * self.sample_size)
  829. else :
  830. self.n_samples = self.nbpt + self.nbptEx * 32768
  831. ep_size = self.preSeqI + self.postSeqI
  832. if not self.no_analog_data :
  833. ep_size += self.n_samples * self.sample_size * self.n_channels
  834. self.ep_size = ep_size
  835. self.n_episodes = (self.dat_length / self.ep_size) if (self.n_samples != 0) else 0
  836. class DAC2GSHeader(Header):
  837. """
  838. A subclass of :class:`Header` used to
  839. identify the 'DAC2/GS/2000' format.
  840. NB : the size is fixed to 20 bytes,
  841. the identifier is a string containing
  842. 15 characters and the size is encoded
  843. as integer.
  844. """
  845. def __init__(self, layout):
  846. super(DAC2GSHeader, self).__init__(layout, "DAC2/GS/2000", 20, 15, "i")
  847. class DAC2Header(Header):
  848. """
  849. A subclass of :class:`Header` used to
  850. identify the 'DAC2 objects' format.
  851. NB : the size is fixed to 18 bytes,
  852. the identifier is a string containing
  853. 15 characters and the size is encoded
  854. as small integer.
  855. """
  856. def __init__(self, layout):
  857. super(DAC2Header, self).__init__(layout, "DAC2 objects", 18, 15, "h")
  858. class DAC2GSMainBlock(ElphyBlock):
  859. """
  860. Subclass of :class:`Block` useful to store data corresponding to
  861. the 'Main' block stored in the DAC2/GS/2000 format :
  862. ``n_channels`` : the number of acquisition channels.
  863. ``nbpt`` : the number of samples by episodes.
  864. ``tpData`` : the data format identifier used to compute sample size.
  865. ``x_unit`` : the x-coordinate unit for all channels in an episode.
  866. ``y_units`` : an array containing y-coordinate units for each channel in the episode.
  867. ``dX`` and ``X0`` : the scale factors necessary to retrieve the actual
  868. times relative to each sample in a channel.
  869. ``dY_ar`` and ``Y0_ar``: arrays of scale factors necessary to retrieve
  870. the actual values relative to samples.
  871. ``continuous`` : a boolean telling if the file has been acquired in
  872. continuous mode.
  873. ``preSeqI`` : the size in bytes of the data preceding raw data.
  874. ``postSeqI`` : the size in bytes of the data preceding raw data.
  875. ``withTags`` : a boolean telling if tags are recorded.
  876. ``tagShift`` : the number of tag channels and the shift to apply
  877. to encoded values to retrieve acquired values.
  878. ``dat_length`` : the length in bytes of the data in the file.
  879. ``sample_size`` : the size in bytes of a sample.
  880. ``n_samples`` : the number of samples.
  881. ``ep_size`` : the size in bytes of an episode.
  882. ``n_episodes`` : the number of recording sequences store in the file.
  883. NB : see file 'FdefDac2.pas' to identify the other parsed parameters.
  884. """
  885. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="i"):
  886. super(DAC2GSMainBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  887. #parse the file to retrieve episodes and channels properties
  888. n_channels, nbpt, tpData = struct.unpack('<BiB', layout.file.read(6))
  889. l_xu, xu, dX, X0 = struct.unpack('<B10sdd', layout.file.read(27))
  890. y_units = list()
  891. dY_ar = list()
  892. Y0_ar = list()
  893. for _ in range(0, 16) :
  894. l_yu, yu, dY, Y0 = struct.unpack('<B10sdd', layout.file.read(27))
  895. y_units.append(yu[0:l_yu])
  896. dY_ar.append(dY)
  897. Y0_ar.append(Y0)
  898. preSeqI, postSeqI, continuous, varEp, withTags = struct.unpack('<ii???', layout.file.read(11))
  899. #some file doesn't precise the tagShift
  900. position = layout.file.tell()
  901. if position >= self.end :
  902. tagShift = 0
  903. else :
  904. tagShift = read_from_char(layout.file, 'B')
  905. #setup object properties
  906. self.n_channels = n_channels
  907. self.nbpt = nbpt
  908. self.tpData = tpData
  909. self.x_unit = xu[0:l_xu]
  910. self.dX = dX
  911. self.X0 = X0
  912. self.y_units = y_units[0:n_channels]
  913. self.dY_ar = dY_ar[0:n_channels]
  914. self.Y0_ar = Y0_ar[0:n_channels]
  915. self.continuous = continuous
  916. if self.continuous :
  917. self.preSeqI = 0
  918. self.postSeqI = 0
  919. else :
  920. self.preSeqI = preSeqI
  921. self.postSeqI = postSeqI
  922. self.varEp = varEp
  923. self.withTags = withTags
  924. if not self.withTags :
  925. self.tagShift = 0
  926. else :
  927. if tagShift == 0 :
  928. self.tagShift = 4
  929. else :
  930. self.tagShift = tagShift
  931. self.sample_size = type_dict[types[self.tpData]]
  932. self.dat_length = self.layout.file_size - self.layout.data_offset
  933. if self.continuous :
  934. if self.n_channels > 0 :
  935. self.n_samples = self.dat_length / (self.n_channels * self.sample_size)
  936. else :
  937. self.n_samples = 0
  938. else :
  939. self.n_samples = self.nbpt
  940. self.ep_size = self.preSeqI + self.postSeqI + self.n_samples * self.sample_size * self.n_channels
  941. self.n_episodes = self.dat_length / self.ep_size if (self.n_samples != 0) else 0
  942. class DAC2GSEpisodeBlock(ElphyBlock):
  943. """
  944. Subclass of :class:`Block` useful to store data corresponding to
  945. 'DAC2SEQ' blocks stored in the DAC2/GS/2000 format.
  946. ``n_channels`` : the number of acquisition channels.
  947. ``nbpt`` : the number of samples by episodes.
  948. ``tpData`` : the data format identifier used to compute the sample size.
  949. ``x_unit`` : the x-coordinate unit for all channels in an episode.
  950. ``y_units`` : an array containing y-coordinate units for each channel in the episode.
  951. ``dX`` and ``X0`` : the scale factors necessary to retrieve the actual
  952. times relative to each sample in a channel.
  953. ``dY_ar`` and ``Y0_ar``: arrays of scale factors necessary to retrieve
  954. the actual values relative to samples.
  955. ``postSeqI`` : the size in bytes of the data preceding raw data.
  956. NB : see file 'FdefDac2.pas' to identify the parsed parameters.
  957. """
  958. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="i"):
  959. main = layout.main_block
  960. n_channels, nbpt, tpData, postSeqI = struct.unpack('<BiBi', layout.file.read(10))
  961. l_xu, xu, dX, X0 = struct.unpack('<B10sdd', layout.file.read(27))
  962. y_units = list()
  963. dY_ar = list()
  964. Y0_ar = list()
  965. for _ in range(0, 16) :
  966. l_yu, yu, dY, Y0 = struct.unpack('<B10sdd', layout.file.read(27))
  967. y_units.append(yu[0:l_yu])
  968. dY_ar.append(dY)
  969. Y0_ar.append(Y0)
  970. super(DAC2GSEpisodeBlock, self).__init__(layout, identifier, start, layout.main_block.ep_size, fixed_length, size_format)
  971. self.n_channels = main.n_channels
  972. self.nbpt = main.nbpt
  973. self.tpData = main.tpData
  974. if not main.continuous :
  975. self.postSeqI = postSeqI
  976. self.x_unit = xu[0:l_xu]
  977. self.dX = dX
  978. self.X0 = X0
  979. self.y_units = y_units[0:n_channels]
  980. self.dY_ar = dY_ar[0:n_channels]
  981. self.Y0_ar = Y0_ar[0:n_channels]
  982. else :
  983. self.postSeqI = 0
  984. self.x_unit = main.x_unit
  985. self.dX = main.dX
  986. self.X0 = main.X0
  987. self.y_units = main.y_units
  988. self.dY_ar = main.dY_ar
  989. self.Y0_ar = main.Y0_ar
  990. class DAC2EpisodeBlock(ElphyBlock):
  991. """
  992. Subclass of :class:`Block` useful to store data corresponding to
  993. 'B_Ep' blocks stored in the last version of Elphy format :
  994. ``ep_block`` : a shortcut the the 'Ep' sub-block.
  995. ``ch_block`` : a shortcut the the 'Adc' sub-block.
  996. ``ks_block`` : a shortcut the the 'KSamp' sub-block.
  997. ``kt_block`` : a shortcut the the 'Ktype' sub-block.
  998. """
  999. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l"):
  1000. super(DAC2EpisodeBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  1001. self.ep_block = None
  1002. self.ch_block = None
  1003. self.ks_block = None
  1004. self.kt_block = None
  1005. def set_episode_block(self):
  1006. blocks = self.layout.get_blocks_of_type('Ep', target_blocks=self.sub_blocks)
  1007. self.ep_block = blocks[0] if blocks else None
  1008. def set_channel_block(self):
  1009. blocks = self.layout.get_blocks_of_type('Adc', target_blocks=self.sub_blocks)
  1010. self.ch_block = blocks[0] if blocks else None
  1011. def set_sub_sampling_block(self):
  1012. blocks = self.layout.get_blocks_of_type('Ksamp', target_blocks=self.sub_blocks)
  1013. self.ks_block = blocks[0] if blocks else None
  1014. def set_sample_size_block(self):
  1015. blocks = self.layout.get_blocks_of_type('Ktype', target_blocks=self.sub_blocks)
  1016. self.kt_block = blocks[0] if blocks else None
  1017. class DummyDataBlock(BaseBlock):
  1018. """
  1019. Subclass of :class:`BaseBlock` useful to
  1020. identify chunk of blocks that are actually
  1021. corresponding to acquired data.
  1022. """
  1023. pass
  1024. class DAC2RDataBlock(ElphyBlock):
  1025. """
  1026. Subclass of :class:`Block` useful to store data corresponding to
  1027. 'RDATA' blocks stored in the last version of Elphy format :
  1028. ``data_start`` : the starting point of raw data.
  1029. NB : This kind of block is preceeded by a structure which size is encoded
  1030. as a 2 bytes unsigned short. Consequently, data start at data_offset plus
  1031. the size.
  1032. """
  1033. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l"):
  1034. super(DAC2RDataBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  1035. self.data_start = self.data_offset + read_from_char(layout.file, 'H')
  1036. class DAC2CyberTagBlock(ElphyBlock):
  1037. """
  1038. Subclass of :class:`Block` useful to store data corresponding to
  1039. 'RCyberTag' blocks stored in the last version of Elphy format :
  1040. ``data_start`` : the starting point of raw data.
  1041. NB : This kind of block is preceeded by a structure which size is encoded
  1042. as a 2 bytes unsigned short. Consequently, data start at data_offset plus
  1043. the size.
  1044. """
  1045. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l"):
  1046. super(DAC2CyberTagBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  1047. self.data_start = self.data_offset + read_from_char(layout.file, 'H')
  1048. class DAC2EventBlock(ElphyBlock):
  1049. """
  1050. Subclass of :class:`Block` useful to store
  1051. data corresponding to 'REVT' blocks stored
  1052. in the last version of Elphy format :
  1053. ``data_start`` : the starting point of raw data.
  1054. ``n_evt_channels`` : the number of channels used to acquire events.
  1055. ``n_events`` : an array containing the number of events for each event channel.
  1056. """
  1057. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l"):
  1058. super(DAC2EventBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  1059. fileobj = self.layout.file
  1060. jump = self.data_offset + read_from_char(fileobj, 'H')
  1061. fileobj.seek(jump)
  1062. #extract the number of event channel
  1063. self.n_evt_channels = read_from_char(fileobj, 'i')
  1064. # extract for each event channel
  1065. # the corresponding number of events
  1066. n_events = list()
  1067. for _ in range(0, self.n_evt_channels) :
  1068. n_events.append(read_from_char(fileobj, 'i'))
  1069. self.n_events = n_events
  1070. self.data_start = fileobj.tell()
  1071. class DAC2SpikeBlock(DAC2EventBlock):
  1072. """
  1073. Subclass of :class:`DAC2EventBlock` useful
  1074. to identify 'RSPK' and make the distinction
  1075. with 'REVT' blocks stored in the last version
  1076. of Elphy format.
  1077. """
  1078. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l"):
  1079. super(DAC2SpikeBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  1080. fileobj = self.layout.file
  1081. jump = self.data_offset
  1082. fileobj.seek(jump) # go to SpikeBlock
  1083. jump = self.data_offset + read_from_char(fileobj, 'h')
  1084. fileobj.seek(jump)
  1085. #extract the number of event channel
  1086. self.n_evt_channels = read_from_char(fileobj, 'i')
  1087. # extract for each event channel
  1088. # the corresponding number of events
  1089. n_events = list()
  1090. for _ in range(0, self.n_evt_channels) :
  1091. n_events.append(read_from_char(fileobj, 'i'))
  1092. self.n_events = n_events
  1093. self.data_start = fileobj.tell()
  1094. class DAC2WaveFormBlock(ElphyBlock):
  1095. """
  1096. Subclass of :class:`Block` useful to store data corresponding to
  1097. 'RspkWave' blocks stored in the last version of Elphy format :
  1098. ``data_start`` : the starting point of raw data.
  1099. ``n_spk_channels`` : the number of channels used to acquire spiketrains.
  1100. ``n_spikes`` : an array containing the number of spikes for each spiketrain.
  1101. ``pre_trigger`` : the number of samples of a waveform arriving before a spike.
  1102. ``wavelength`` : the number of samples in a waveform.
  1103. """
  1104. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l"):
  1105. super(DAC2WaveFormBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format)
  1106. fileobj = self.layout.file
  1107. jump = self.data_offset + read_from_char(fileobj, 'H')
  1108. fileobj.seek(jump)
  1109. self.wavelength = read_from_char(fileobj, 'i')
  1110. self.pre_trigger = read_from_char(fileobj, 'i')
  1111. self.n_spk_channels = read_from_char(fileobj, 'i')
  1112. n_spikes = list()
  1113. for _ in range(0, self.n_spk_channels) :
  1114. n_spikes.append(read_from_char(fileobj, 'i'))
  1115. self.n_spikes = n_spikes
  1116. self.data_start = fileobj.tell()
  1117. class DAC2EpSubBlock(ElphyBlock):
  1118. """
  1119. Subclass of :class:`Block` useful to retrieve data corresponding
  1120. to a 'Ep' sub-block stored in the last version of Elphy format :
  1121. ``n_channels`` : the number of acquisition channels.
  1122. ``nbpt`` : the number of samples by episodes
  1123. ``tpData`` : the data format identifier used to store signal samples.
  1124. ``x_unit`` : the x-coordinate unit for all channels in an episode.
  1125. ``dX`` and ``X0`` : the scale factors necessary to retrieve the actual
  1126. times relative to each sample in a channel.
  1127. ``continuous`` : a boolean telling if the file has been acquired in
  1128. continuous mode.
  1129. ``tag_mode`` : identify the way tags are stored in a file.
  1130. ``tag_shift`` : the number of bits that tags occupy in a 16-bits sample
  1131. and the shift necessary to do to retrieve the value of the sample.
  1132. ``dX_wf`` and ``X0_wf``: the scale factors necessary to retrieve the actual
  1133. times relative to each waveforms.
  1134. ``dY_wf`` and ``Y0_wf``: the scale factors necessary to retrieve the actual
  1135. values relative to waveform samples.
  1136. ``x_unit_wf`` and ``y_unit_wf``: the unit of x and y coordinates for all waveforms in an episode.
  1137. """
  1138. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l", parent_block=None):
  1139. super(DAC2EpSubBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format, parent_block=parent_block)
  1140. fileobj = self.layout.file
  1141. n_channels, nbpt, tpData, l_xu, x_unit, dX, X0 = struct.unpack('<BiBB10sdd', fileobj.read(33))
  1142. continuous, tag_mode, tag_shift = struct.unpack('<?BB', fileobj.read(3))
  1143. DxuSpk, X0uSpk, nbSpk, DyuSpk, Y0uSpk, l_xuspk, unitXSpk, l_yuspk, unitYSpk = struct.unpack('<ddiddB10sB10s', fileobj.read(58))
  1144. cyber_time, pc_time = struct.unpack('<dI', fileobj.read(12))
  1145. # necessary properties to reconstruct
  1146. # signals stored into the file
  1147. self.n_channels = n_channels
  1148. self.nbpt = nbpt
  1149. self.tpData = tpData
  1150. self.x_unit = x_unit[0:l_xu]
  1151. self.dX = dX
  1152. self.X0 = X0
  1153. self.continuous = continuous
  1154. self.tag_mode = tag_mode
  1155. self.tag_shift = tag_shift if self.tag_mode == 1 else 0
  1156. # following properties are valid
  1157. # when using multielectrode system
  1158. # named BlackRock / Cyberkinetics
  1159. #if fileobj.tell() < self.end :
  1160. self.dX_wf = DxuSpk
  1161. self.X0_wf = X0uSpk
  1162. self.n_spikes = nbSpk
  1163. self.dY_wf = DyuSpk
  1164. self.Y0_wf = Y0uSpk
  1165. self.x_unit_wf = unitXSpk[0:l_xuspk]
  1166. self.y_unit_wf = unitYSpk[0:l_yuspk]
  1167. self.cyber_time = cyber_time
  1168. self.pc_time = pc_time
  1169. class DAC2AdcSubBlock(ElphyBlock):
  1170. """
  1171. Subclass of :class:`SubBlock` useful to retrieve data corresponding
  1172. to a 'Adc' sub-block stored in the last version of Elphy format :
  1173. ``y_units`` : an array containing all y-coordinates for each channel.
  1174. ``dY_ar`` and ``Y0_ar`` : arrays containing scaling factors for each
  1175. channel useful to compute the actual value of a signal sample.
  1176. """
  1177. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l", parent_block=None):
  1178. super(DAC2AdcSubBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format, parent_block=parent_block)
  1179. fileobj = self.layout.file
  1180. #fileobj.seek(start + len(identifier) + 1)
  1181. ep_block, = [k for k in self.parent_block.sub_blocks if k.identifier.startswith('Ep')]
  1182. n_channels = ep_block.n_channels
  1183. self.y_units = list()
  1184. self.dY_ar = list()
  1185. self.Y0_ar = list()
  1186. for _ in range(0, n_channels) :
  1187. l_yu, y_unit, dY, Y0 = struct.unpack('<B10sdd', fileobj.read(27))
  1188. self.y_units.append(y_unit[0:l_yu])
  1189. self.dY_ar.append(dY)
  1190. self.Y0_ar.append(Y0)
  1191. class DAC2KSampSubBlock(ElphyBlock):
  1192. """
  1193. Subclass of :class:`SubBlock` useful to retrieve data corresponding
  1194. to a 'Ksamp' sub-block stored in the last version of Elphy format :
  1195. ``k_sampling`` : an array containing all sub-sampling factors
  1196. corresponding to each acquired channel. If a factor is equal to
  1197. zero, then the channel has been converted into an event channel.
  1198. """
  1199. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l", parent_block=None):
  1200. super(DAC2KSampSubBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format, parent_block=parent_block)
  1201. fileobj = self.layout.file
  1202. ep_block, = [k for k in self.parent_block.sub_blocks if k.identifier.startswith('Ep')]
  1203. n_channels = ep_block.n_channels
  1204. k_sampling = list()
  1205. for _ in range(0, n_channels) :
  1206. k_sampling.append(read_from_char(fileobj, "H"))
  1207. self.k_sampling = k_sampling
  1208. class DAC2KTypeSubBlock(ElphyBlock):
  1209. """
  1210. Subclass of :class:`SubBlock` useful to retrieve data corresponding
  1211. to a 'Ktype' sub-block stored in the last version of Elphy format :
  1212. ``k_types`` : an array containing all data formats identifier used
  1213. to compute sample size.
  1214. """
  1215. def __init__(self, layout, identifier, start, size, fixed_length=None, size_format="l", parent_block=None):
  1216. super(DAC2KTypeSubBlock, self).__init__(layout, identifier, start, size, fixed_length, size_format, parent_block=parent_block)
  1217. fileobj = self.layout.file
  1218. ep_block, = [k for k in self.parent_block.sub_blocks if k.identifier.startswith('Ep')]
  1219. n_channels = ep_block.n_channels
  1220. k_types = list()
  1221. for _ in range(0, n_channels) :
  1222. k_types.append(read_from_char(fileobj, "B"))
  1223. self.k_types = k_types
  1224. # --------------------------------------------------------
  1225. # UTILS
  1226. #symbols of types that could
  1227. #encode a value in an elphy file
  1228. types = (
  1229. 'B',
  1230. 'b',
  1231. 'h',
  1232. 'H',
  1233. 'l',
  1234. 'f',
  1235. 'real48',
  1236. 'd',
  1237. 'ext',
  1238. 's_complex',
  1239. 'd_complex',
  1240. 'complex',
  1241. 'none'
  1242. )
  1243. #a dictionary linking python.struct
  1244. #formats to their actual size in bytes
  1245. type_dict = {
  1246. 'c':1,
  1247. 'b':1,
  1248. 'B':1,
  1249. '?':1,
  1250. 'h':2,
  1251. 'H':2,
  1252. 'i':4,
  1253. 'I':4,
  1254. 'l':4,
  1255. 'L':4,
  1256. 'q':8,
  1257. 'Q':8,
  1258. 'f':4,
  1259. 'd':8,
  1260. 'H+l':6,
  1261. 'ext':10,
  1262. 'real48':6,
  1263. 's_complex':8,
  1264. 'd_complex':16,
  1265. 'complex':20,
  1266. 'none':0
  1267. }
  1268. #a dictionary liking python.struct
  1269. #formats to numpy formats
  1270. numpy_map = {
  1271. 'b':np.int8,
  1272. 'B':np.uint8,
  1273. 'h':np.int16,
  1274. 'H':np.uint16,
  1275. 'i':np.int32,
  1276. 'I':np.uint32,
  1277. 'l':np.int32,
  1278. 'L':np.uint32,
  1279. 'q':np.int64,
  1280. 'Q':np.uint64,
  1281. 'f':np.float32,
  1282. 'd':np.float64,
  1283. 'H+l':6,
  1284. 'ext':10,
  1285. 'real48':6,
  1286. 'SComp':8,
  1287. 'DComp':16,
  1288. 'Comp':20,
  1289. 'none':0
  1290. }
  1291. def read_from_char(data, type_char):
  1292. """
  1293. Return the value corresponding
  1294. to the specified character type.
  1295. """
  1296. n_bytes = type_dict[type_char]
  1297. ascii = data.read(n_bytes) if isinstance(data, file) else data
  1298. if type_char != 'ext':
  1299. try :
  1300. value = struct.unpack('<%s' % type_char, ascii)[0]
  1301. except :
  1302. # the value could not been read
  1303. # because the value is not compatible
  1304. # with the specified type
  1305. value = None
  1306. else :
  1307. try :
  1308. value = float(ascii)
  1309. except :
  1310. value = None
  1311. return value
  1312. def least_common_multiple(a, b):
  1313. """
  1314. Return the value of the least common multiple.
  1315. """
  1316. return (a * b) / gcd(a, b)
  1317. # --------------------------------------------------------
  1318. # LAYOUT
  1319. b_float = 'f8'
  1320. b_int = 'i2'
  1321. class ElphyLayout(object):
  1322. """
  1323. A convenient class to know how data
  1324. are organised into an Elphy file :
  1325. ``elphy_file`` : a :class:`ElphyFile`
  1326. asking file introspection.
  1327. ``blocks`` : a set of :class:``BaseBlock`
  1328. objects partitioning a file and extracting
  1329. some useful metadata.
  1330. ``ìnfo_block`` : a shortcut to a :class:`FileInfoBlock`
  1331. object containing metadata describing a recording
  1332. protocol (sparsenoise, densenoise, movingbar or flashbar)
  1333. ``data_blocks`` : a shortcut to access directly
  1334. blocks containing raw data.
  1335. NB : Subclassing this class is a convenient
  1336. way to retrieve blocks constituting a file,
  1337. their relative information and location of
  1338. raw data using polymorphism rather than a
  1339. conditional structure.
  1340. """
  1341. def __init__(self, elphy_file):
  1342. self.elphy_file = elphy_file
  1343. self.blocks = list()
  1344. self.info_block = None
  1345. self.data_blocks = None
  1346. @property
  1347. def file(self):
  1348. return self.elphy_file.file
  1349. @property
  1350. def file_size(self):
  1351. return self.elphy_file.file_size
  1352. def is_continuous(self):
  1353. return self.is_continuous()
  1354. def add_block(self, block):
  1355. self.blocks.append(block)
  1356. @property
  1357. def header(self):
  1358. return self.blocks[0]
  1359. def get_blocks_of_type(self, identifier, target_blocks=None):
  1360. blocks = self.blocks if target_blocks is None else target_blocks
  1361. return [k for k in blocks if (k.identifier == identifier)]
  1362. def set_info_block(self):
  1363. raise NotImplementedError('must be overloaded in a subclass')
  1364. def set_data_blocks(self):
  1365. raise NotImplementedError('must be overloaded in a subclass')
  1366. def get_tag(self, episode, tag_channel):
  1367. raise NotImplementedError('must be overloaded in a subclass')
  1368. @property
  1369. def n_episodes(self):
  1370. raise NotImplementedError('must be overloaded in a subclass')
  1371. def n_channels(self, episode):
  1372. raise NotImplementedError('must be overloaded in a subclass')
  1373. def n_tags(self, episode):
  1374. raise NotImplementedError('must be overloaded in a subclass')
  1375. def n_samples(self, episode, channel):
  1376. raise NotImplementedError('must be overloaded in a subclass')
  1377. def sample_type(self, ep, ch):
  1378. raise NotImplementedError('must be overloaded in a subclass')
  1379. def sample_size(self, ep, ch):
  1380. symbol = self.sample_symbol(ep, ch)
  1381. return type_dict[symbol]
  1382. def sample_symbol(self, ep, ch):
  1383. tp = self.sample_type(ep, ch)
  1384. try:
  1385. return types[tp]
  1386. except :
  1387. return 'h'
  1388. def sampling_period(self, ep, ch):
  1389. raise NotImplementedError('must be overloaded in a subclass')
  1390. def x_scale_factors(self, ep, ch):
  1391. raise NotImplementedError('must be overloaded in a subclass')
  1392. def y_scale_factors(self, ep, ch):
  1393. raise NotImplementedError('must be overloaded in a subclass')
  1394. def x_tag_scale_factors(self, ep):
  1395. raise NotImplementedError('must be overloaded in a subclass')
  1396. def x_unit(self, ep, ch):
  1397. raise NotImplementedError('must be overloaded in a subclass')
  1398. def y_unit(self, ep, ch):
  1399. raise NotImplementedError('must be overloaded in a subclass')
  1400. def tag_shift(self, ep):
  1401. raise NotImplementedError('must be overloaded in a subclass')
  1402. def get_channel_for_tags(self, ep):
  1403. raise NotImplementedError('must be overloaded in a subclass')
  1404. def get_signal(self, episode, channel):
  1405. """
  1406. Return the signal description relative
  1407. to the specified episode and channel.
  1408. """
  1409. assert episode in range(1, self.n_episodes + 1)
  1410. assert channel in range(1, self.n_channels(episode) + 1)
  1411. t_start = 0
  1412. sampling_period = self.sampling_period(episode, channel)
  1413. t_stop = sampling_period * self.n_samples(episode, channel)
  1414. return ElphySignal(
  1415. self,
  1416. episode,
  1417. channel,
  1418. self.x_unit(episode, channel),
  1419. self.y_unit(episode, channel),
  1420. 1 / sampling_period,
  1421. t_start,
  1422. t_stop
  1423. )
  1424. def create_channel_mask(self, ep):
  1425. """
  1426. Return the minimal pattern of channel numbers
  1427. representing the succession of channels in the
  1428. multiplexed data. It is necessary to do the mapping
  1429. between a sample stored in the file and its relative
  1430. channel.
  1431. """
  1432. raise NotImplementedError('must be overloaded in a subclass')
  1433. def get_data_blocks(self, ep):
  1434. """
  1435. Return a set of :class:`DummyDataBlock` instances
  1436. that defined the actual location of samples in blocks
  1437. encapsulating raw data.
  1438. """
  1439. raise NotImplementedError('must be overloaded in a subclass')
  1440. def create_bit_mask(self, ep, ch):
  1441. """
  1442. Build a mask to apply on the entire episode
  1443. in order to only keep values corresponding
  1444. to the specified channel.
  1445. """
  1446. ch_mask = self.create_channel_mask(ep)
  1447. _mask = list()
  1448. for _ch in ch_mask :
  1449. size = self.sample_size(ep, _ch)
  1450. val = 1 if _ch == ch else 0
  1451. for _ in xrange(0, size) :
  1452. _mask.append(val)
  1453. return np.array(_mask)
  1454. def load_bytes(self, data_blocks, dtype='<i1', start=None, end=None, expected_size=None):
  1455. """
  1456. Return list of bytes contained
  1457. in the specified set of blocks.
  1458. NB : load all data as files cannot exceed 4Gb
  1459. find later other solutions to spare memory.
  1460. """
  1461. chunks = list()
  1462. raw = ''
  1463. # keep only data blocks having
  1464. # a size greater than zero
  1465. blocks = [k for k in data_blocks if k.size > 0]
  1466. for data_block in blocks :
  1467. self.file.seek(data_block.start)
  1468. raw = self.file.read(data_block.size)[0:expected_size]
  1469. databytes = np.frombuffer(raw, dtype=dtype)
  1470. chunks.append(databytes)
  1471. # concatenate all chunks and return
  1472. # the specified slice
  1473. if len(chunks)>0 :
  1474. databytes = np.concatenate(chunks)
  1475. return databytes[start:end]
  1476. else :
  1477. return np.array([])
  1478. def reshape_bytes(self, databytes, reshape, datatypes, order='<'):
  1479. """
  1480. Reshape a numpy array containing a set of databytes.
  1481. """
  1482. assert datatypes and len(datatypes) == len(reshape), "datatypes are not well defined"
  1483. l_bytes = len(databytes)
  1484. #create the mask for each shape
  1485. shape_mask = list()
  1486. for shape in reshape :
  1487. for _ in xrange(1, shape + 1) :
  1488. shape_mask.append(shape)
  1489. #create a set of masks to extract data
  1490. bit_masks = list()
  1491. for shape in reshape :
  1492. bit_mask = list()
  1493. for value in shape_mask :
  1494. bit = 1 if (value == shape) else 0
  1495. bit_mask.append(bit)
  1496. bit_masks.append(np.array(bit_mask))
  1497. #extract data
  1498. n_samples = l_bytes / np.sum(reshape)
  1499. data = np.empty([len(reshape), n_samples], dtype=(int, int))
  1500. for index, bit_mask in enumerate(bit_masks) :
  1501. tmp = self.filter_bytes(databytes, bit_mask)
  1502. tp = '%s%s%s' % (order, datatypes[index], reshape[index])
  1503. data[index] = np.frombuffer(tmp, dtype=tp)
  1504. return data.T
  1505. def filter_bytes(self, databytes, bit_mask):
  1506. """
  1507. Detect from a bit mask which bits
  1508. to keep to recompose the signal.
  1509. """
  1510. n_bytes = len(databytes)
  1511. mask = np.ones(n_bytes, dtype=int)
  1512. np.putmask(mask, mask, bit_mask)
  1513. to_keep = np.where(mask > 0)[0]
  1514. return databytes.take(to_keep)
  1515. def load_channel_data(self, ep, ch):
  1516. """
  1517. Return a numpy array containing the
  1518. list of bytes corresponding to the
  1519. specified episode and channel.
  1520. """
  1521. #memorise the sample size and symbol
  1522. sample_size = self.sample_size(ep, ch)
  1523. sample_symbol = self.sample_symbol(ep, ch)
  1524. #create a bit mask to define which
  1525. #sample to keep from the file
  1526. bit_mask = self.create_bit_mask(ep, ch)
  1527. #load all bytes contained in an episode
  1528. data_blocks = self.get_data_blocks(ep)
  1529. databytes = self.load_bytes(data_blocks)
  1530. raw = self.filter_bytes(databytes, bit_mask)
  1531. #reshape bytes from the sample size
  1532. dt = np.dtype(numpy_map[sample_symbol])
  1533. dt.newbyteorder('<')
  1534. return np.frombuffer(raw.reshape([len(raw) / sample_size, sample_size]), dt)
  1535. def apply_op(self, np_array, value, op_type):
  1536. """
  1537. A convenient function to apply an operator
  1538. over all elements of a numpy array.
  1539. """
  1540. if op_type == "shift_right" :
  1541. return np_array >> value
  1542. elif op_type == "shift_left" :
  1543. return np_array << value
  1544. elif op_type == "mask" :
  1545. return np_array & value
  1546. else :
  1547. return np_array
  1548. def get_tag_mask(self, tag_ch, tag_mode):
  1549. """
  1550. Return a mask useful to retrieve
  1551. bits that encode a tag channel.
  1552. """
  1553. if tag_mode == 1 :
  1554. tag_mask = 0b01 if (tag_ch == 1) else 0b10
  1555. elif tag_mode in [2, 3] :
  1556. ar_mask = np.zeros(16, dtype=int)
  1557. ar_mask[tag_ch - 1] = 1
  1558. st = "0b" + ''.join(np.array(np.flipud(ar_mask), dtype=str))
  1559. tag_mask = eval(st)
  1560. return tag_mask
  1561. def load_encoded_tags(self, ep, tag_ch):
  1562. """
  1563. Return a numpy array containing
  1564. bytes corresponding to the specified
  1565. episode and channel.
  1566. """
  1567. tag_mode = self.tag_mode(ep)
  1568. tag_mask = self.get_tag_mask(tag_ch, tag_mode)
  1569. if tag_mode in [1, 2] :
  1570. #digidata or itc mode
  1571. #available for all formats
  1572. ch = self.get_channel_for_tags(ep)
  1573. raw = self.load_channel_data(ep, ch)
  1574. return self.apply_op(raw, tag_mask, "mask")
  1575. elif tag_mode == 3 :
  1576. #cyber k mode
  1577. #only available for DAC2 objects format
  1578. #store bytes corresponding to the blocks
  1579. #containing tags in a numpy array and reshape
  1580. #it to have a set of tuples (time, value)
  1581. ck_blocks = self.get_blocks_of_type(ep, 'RCyberTag')
  1582. databytes = self.load_bytes(ck_blocks)
  1583. raw = self.reshape_bytes(databytes, reshape=(4, 2), datatypes=('u', 'u'), order='<')
  1584. #keep only items that are compatible
  1585. #with the specified tag channel
  1586. raw[:, 1] = self.apply_op(raw[:, 1], tag_mask, "mask")
  1587. #computing numpy.diff is useful to know
  1588. #how many times a value is maintained
  1589. #and necessary to reconstruct the
  1590. #compressed signal ...
  1591. repeats = np.array(np.diff(raw[:, 0]), dtype=int)
  1592. data = np.repeat(raw[:-1, 1], repeats, axis=0)
  1593. # ... note that there is always
  1594. #a transition at t=0 for synchronisation
  1595. #purpose, consequently it is not necessary
  1596. #to complete with zeros when the first
  1597. #transition arrive ...
  1598. return data
  1599. def load_encoded_data(self, ep, ch):
  1600. """
  1601. Get encoded value of raw data from the elphy file.
  1602. """
  1603. tag_shift = self.tag_shift(ep)
  1604. data = self.load_channel_data(ep, ch)
  1605. if tag_shift :
  1606. return self.apply_op(data, tag_shift, "shift_right")
  1607. else :
  1608. return data
  1609. def get_signal_data(self, ep, ch):
  1610. """
  1611. Return a numpy array containing all samples of a
  1612. signal, acquired on an Elphy analog channel, formatted
  1613. as a list of (time, value) tuples.
  1614. """
  1615. #get data from the file
  1616. y_data = self.load_encoded_data(ep, ch)
  1617. x_data = np.arange(0, len(y_data))
  1618. #create a recarray
  1619. data = np.recarray(len(y_data), dtype=[('x', b_float), ('y', b_float)])
  1620. #put in the recarray the scaled data
  1621. x_factors = self.x_scale_factors(ep, ch)
  1622. y_factors = self.y_scale_factors(ep, ch)
  1623. data['x'] = x_factors.scale(x_data)
  1624. data['y'] = y_factors.scale(y_data)
  1625. return data
  1626. def get_tag_data(self, ep, tag_ch):
  1627. """
  1628. Return a numpy array containing all samples of a
  1629. signal, acquired on an Elphy tag channel, formatted
  1630. as a list of (time, value) tuples.
  1631. """
  1632. #get data from the file
  1633. y_data = self.load_encoded_tags(ep, tag_ch)
  1634. x_data = np.arange(0, len(y_data))
  1635. #create a recarray
  1636. data = np.recarray(len(y_data), dtype=[('x', b_float), ('y', b_int)])
  1637. #put in the recarray the scaled data
  1638. factors = self.x_tag_scale_factors(ep)
  1639. data['x'] = factors.scale(x_data)
  1640. data['y'] = y_data
  1641. return data
  1642. class Acquis1Layout(ElphyLayout):
  1643. """
  1644. A subclass of :class:`ElphyLayout` to know
  1645. how the 'ACQUIS1/GS/1991' format is organised.
  1646. Extends :class:`ElphyLayout` to store the
  1647. offset used to retrieve directly raw data :
  1648. ``data_offset`` : an offset to jump directly
  1649. to the raw data.
  1650. """
  1651. def __init__(self, fileobj, data_offset):
  1652. super(Acquis1Layout, self).__init__(fileobj)
  1653. self.data_offset = data_offset
  1654. self.data_blocks = None
  1655. def get_blocks_end(self):
  1656. return self.data_offset
  1657. def is_continuous(self):
  1658. return self.header.continuous
  1659. def get_episode_blocks(self):
  1660. raise NotImplementedError()
  1661. def set_info_block(self):
  1662. i_blks = self.get_blocks_of_type('USER INFO')
  1663. assert len(i_blks) < 2, 'too many info blocks'
  1664. if len(i_blks) :
  1665. self.info_block = i_blks[0]
  1666. def set_data_blocks(self):
  1667. data_blocks = list()
  1668. size = self.header.n_samples * self.header.sample_size * self.header.n_channels
  1669. for ep in range(0, self.header.n_episodes) :
  1670. start = self.data_offset + ep * self.header.ep_size + self.header.preSeqI
  1671. data_blocks.append(DummyDataBlock(self, 'Acquis1Data', start, size))
  1672. self.data_blocks = data_blocks
  1673. def get_data_blocks(self, ep):
  1674. return [self.data_blocks[ep - 1]]
  1675. @property
  1676. def n_episodes(self):
  1677. return self.header.n_episodes
  1678. def n_channels(self, episode):
  1679. return self.header.n_channels
  1680. def n_tags(self, episode):
  1681. return 0
  1682. def tag_mode(self, ep):
  1683. return 0
  1684. def tag_shift(self, ep):
  1685. return 0
  1686. def get_channel_for_tags(self, ep):
  1687. return None
  1688. @property
  1689. def no_analog_data(self):
  1690. return True if (self.n_episodes == 0) else self.header.no_analog_data
  1691. def sample_type(self, ep, ch):
  1692. return self.header.tpData
  1693. def sampling_period(self, ep, ch):
  1694. return self.header.dX
  1695. def n_samples(self, ep, ch):
  1696. return self.header.n_samples
  1697. def x_tag_scale_factors(self, ep):
  1698. return ElphyScaleFactor(
  1699. self.header.dX,
  1700. self.header.X0
  1701. )
  1702. def x_scale_factors(self, ep, ch):
  1703. return ElphyScaleFactor(
  1704. self.header.dX,
  1705. self.header.X0
  1706. )
  1707. def y_scale_factors(self, ep, ch):
  1708. dY = self.header.dY_ar[ch - 1]
  1709. Y0 = self.header.Y0_ar[ch - 1]
  1710. # TODO: see why this kind of exception exists
  1711. if dY is None or Y0 is None :
  1712. raise Exception('bad Y-scale factors for episode %s channel %s' % (ep, ch))
  1713. return ElphyScaleFactor(dY, Y0)
  1714. def x_unit(self, ep, ch):
  1715. return self.header.x_unit
  1716. def y_unit(self, ep, ch):
  1717. return self.header.y_units[ch - 1]
  1718. @property
  1719. def ep_size(self):
  1720. return self.header.ep_size
  1721. @property
  1722. def file_duration(self):
  1723. return self.header.dX * self.n_samples
  1724. def get_tag(self, episode, tag_channel):
  1725. return None
  1726. def create_channel_mask(self, ep):
  1727. return np.arange(1, self.header.n_channels + 1)
  1728. class DAC2GSLayout(ElphyLayout):
  1729. """
  1730. A subclass of :class:`ElphyLayout` to know
  1731. how the 'DAC2 / GS / 2000' format is organised.
  1732. Extends :class:`ElphyLayout` to store the
  1733. offset used to retrieve directly raw data :
  1734. ``data_offset`` : an offset to jump directly
  1735. after the 'MAIN' block where 'DAC2SEQ' blocks
  1736. start.
  1737. ``main_block```: a shortcut to access 'MAIN' block.
  1738. ``episode_blocks`` : a shortcut to access blocks
  1739. corresponding to episodes.
  1740. """
  1741. def __init__(self, fileobj, data_offset):
  1742. super(DAC2GSLayout, self).__init__(fileobj)
  1743. self.data_offset = data_offset
  1744. self.main_block = None
  1745. self.episode_blocks = None
  1746. def get_blocks_end(self):
  1747. return self.file_size #data_offset
  1748. def is_continuous(self):
  1749. main_block = self.main_block
  1750. return main_block.continuous if main_block else False
  1751. def get_episode_blocks(self):
  1752. raise NotImplementedError()
  1753. def set_main_block(self):
  1754. main_block = self.get_blocks_of_type('MAIN')
  1755. self.main_block = main_block[0] if main_block else None
  1756. def set_episode_blocks(self):
  1757. ep_blocks = self.get_blocks_of_type('DAC2SEQ')
  1758. self.episode_blocks = ep_blocks if ep_blocks else None
  1759. def set_info_block(self):
  1760. i_blks = self.get_blocks_of_type('USER INFO')
  1761. assert len(i_blks) < 2, "too many info blocks"
  1762. if len(i_blks) :
  1763. self.info_block = i_blks[0]
  1764. def set_data_blocks(self):
  1765. data_blocks = list()
  1766. identifier = 'DAC2GSData'
  1767. size = self.main_block.n_samples * self.main_block.sample_size * self.main_block.n_channels
  1768. if not self.is_continuous() :
  1769. blocks = self.get_blocks_of_type('DAC2SEQ')
  1770. for block in blocks :
  1771. start = block.start + self.main_block.preSeqI
  1772. data_blocks.append(DummyDataBlock(self, identifier, start, size))
  1773. else :
  1774. start = self.blocks[-1].end + 1 + self.main_block.preSeqI
  1775. data_blocks.append(DummyDataBlock(self, identifier, start, size))
  1776. self.data_blocks = data_blocks
  1777. def get_data_blocks(self, ep):
  1778. return [self.data_blocks[ep - 1]]
  1779. def episode_block(self, ep):
  1780. return self.main_block if self.is_continuous() else self.episode_blocks[ep - 1]
  1781. def tag_mode(self, ep):
  1782. return 1 if self.main_block.withTags else 0
  1783. def tag_shift(self, ep):
  1784. return self.main_block.tagShift
  1785. def get_channel_for_tags(self, ep):
  1786. return 1
  1787. def sample_type(self, ep, ch):
  1788. return self.main_block.tpData
  1789. def sample_size(self, ep, ch):
  1790. size = super(DAC2GSLayout, self).sample_size(ep, ch)
  1791. assert size == 2, "sample size is always 2 bytes for DAC2/GS/2000 format"
  1792. return size
  1793. def sampling_period(self, ep, ch):
  1794. block = self.episode_block(ep)
  1795. return block.dX
  1796. def x_tag_scale_factors(self, ep):
  1797. block = self.episode_block(ep)
  1798. return ElphyScaleFactor(
  1799. block.dX,
  1800. block.X0,
  1801. )
  1802. def x_scale_factors(self, ep, ch):
  1803. block = self.episode_block(ep)
  1804. return ElphyScaleFactor(
  1805. block.dX,
  1806. block.X0,
  1807. )
  1808. def y_scale_factors(self, ep, ch):
  1809. block = self.episode_block(ep)
  1810. return ElphyScaleFactor(
  1811. block.dY_ar[ch - 1],
  1812. block.Y0_ar[ch - 1]
  1813. )
  1814. def x_unit(self, ep, ch):
  1815. block = self.episode_block(ep)
  1816. return block.x_unit
  1817. def y_unit(self, ep, ch):
  1818. block = self.episode_block(ep)
  1819. return block.y_units[ch - 1]
  1820. def n_samples(self, ep, ch):
  1821. return self.main_block.n_samples
  1822. def ep_size(self, ep):
  1823. return self.main_block.ep_size
  1824. @property
  1825. def n_episodes(self):
  1826. return self.main_block.n_episodes
  1827. def n_channels(self, episode):
  1828. return self.main_block.n_channels
  1829. def n_tags(self, episode):
  1830. return 2 if self.main_block.withTags else 0
  1831. @property
  1832. def file_duration(self):
  1833. return self.main_block.dX * self.n_samples
  1834. def get_tag(self, episode, tag_channel):
  1835. assert episode in range(1, self.n_episodes + 1)
  1836. # there are none or 2 tag channels
  1837. if self.tag_mode(episode) == 1 :
  1838. assert tag_channel in range(1, 3), "DAC2/GS/2000 format support only 2 tag channels"
  1839. block = self.episode_block(episode)
  1840. t_stop = self.main_block.n_samples * block.dX
  1841. return ElphyTag(self, episode, tag_channel, block.x_unit, 1.0 / block.dX, 0, t_stop)
  1842. else :
  1843. return None
  1844. def n_tag_samples(self, ep, tag_channel):
  1845. return self.main_block.n_samples
  1846. def get_tag_data(self, episode, tag_channel):
  1847. #memorise some useful properties
  1848. block = self.episode_block(episode)
  1849. sample_size = self.sample_size(episode, tag_channel)
  1850. sample_symbol = self.sample_symbol(episode, tag_channel)
  1851. #create a bit mask to define which
  1852. #sample to keep from the file
  1853. channel_mask = self.create_channel_mask(episode)
  1854. bit_mask = self.create_bit_mask(channel_mask, 1)
  1855. #get bytes from the file
  1856. data_block = self.data_blocks[episode - 1]
  1857. n_bytes = data_block.size
  1858. self.file.seek(data_block.start)
  1859. databytes = np.frombuffer(self.file.read(n_bytes), '<i1')
  1860. #detect which bits keep to recompose the tag
  1861. ep_mask = np.ones(n_bytes, dtype=int)
  1862. np.putmask(ep_mask, ep_mask, bit_mask)
  1863. to_keep = np.where(ep_mask > 0)[0]
  1864. raw = databytes.take(to_keep)
  1865. raw = raw.reshape([len(raw) / sample_size, sample_size])
  1866. #create a recarray containing data
  1867. dt = np.dtype(numpy_map[sample_symbol])
  1868. dt.newbyteorder('<')
  1869. tag_mask = 0b01 if (tag_channel == 1) else 0b10
  1870. y_data = np.frombuffer(raw, dt) & tag_mask
  1871. x_data = np.arange(0, len(y_data)) * block.dX + block.X0
  1872. data = np.recarray(len(y_data), dtype=[('x', b_float), ('y', b_int)])
  1873. data['x'] = x_data
  1874. data['y'] = y_data
  1875. return data
  1876. def create_channel_mask(self, ep):
  1877. return np.arange(1, self.main_block.n_channels + 1)
  1878. class DAC2Layout(ElphyLayout):
  1879. """
  1880. A subclass of :class:`ElphyLayout` to know
  1881. how the Elphy format is organised.
  1882. Whereas other formats storing raw data at the
  1883. end of the file, 'DAC2 objects' format spreads
  1884. them over multiple blocks :
  1885. ``episode_blocks`` : a shortcut to access blocks
  1886. corresponding to episodes.
  1887. """
  1888. def __init__(self, fileobj):
  1889. super(DAC2Layout, self).__init__(fileobj)
  1890. self.episode_blocks = None
  1891. def get_blocks_end(self):
  1892. return self.file_size
  1893. def is_continuous(self):
  1894. ep_blocks = [k for k in self.blocks if k.identifier.startswith('B_Ep')]
  1895. if ep_blocks :
  1896. ep_block = ep_blocks[0]
  1897. ep_sub_block = ep_block.sub_blocks[0]
  1898. return ep_sub_block.continuous
  1899. else :
  1900. return False
  1901. def set_episode_blocks(self):
  1902. self.episode_blocks = [k for k in self.blocks if k.identifier.startswith('B_Ep')]
  1903. def set_info_block(self):
  1904. #in fact the file info are contained into a single sub-block with an USR identifier
  1905. i_blks = self.get_blocks_of_type('B_Finfo')
  1906. assert len(i_blks) < 2, "too many info blocks"
  1907. if len(i_blks) :
  1908. i_blk = i_blks[0]
  1909. sub_blocks = i_blk.sub_blocks
  1910. if len(sub_blocks) :
  1911. self.info_block = sub_blocks[0]
  1912. def set_data_blocks(self):
  1913. data_blocks = list()
  1914. blocks = self.get_blocks_of_type('RDATA')
  1915. for block in blocks :
  1916. start = block.data_start
  1917. size = block.end + 1 - start
  1918. data_blocks.append(DummyDataBlock(self, 'RDATA', start, size))
  1919. self.data_blocks = data_blocks
  1920. def get_data_blocks(self, ep):
  1921. return self.group_blocks_of_type(ep, 'RDATA')
  1922. def group_blocks_of_type(self, ep, identifier):
  1923. ep_blocks = list()
  1924. blocks = [k for k in self.get_blocks_stored_in_episode(ep) if k.identifier == identifier]
  1925. for block in blocks :
  1926. start = block.data_start
  1927. size = block.end + 1 - start
  1928. ep_blocks.append(DummyDataBlock(self, identifier, start, size))
  1929. return ep_blocks
  1930. def get_blocks_stored_in_episode(self, ep):
  1931. data_blocks = [k for k in self.blocks if k.identifier == 'RDATA']
  1932. n_ep = self.n_episodes
  1933. blk_1 = self.episode_block(ep)
  1934. blk_2 = self.episode_block((ep + 1) % n_ep)
  1935. i_1 = self.blocks.index(blk_1)
  1936. i_2 = self.blocks.index(blk_2)
  1937. if (blk_1 == blk_2) or (i_2 < i_1) :
  1938. return [k for k in data_blocks if self.blocks.index(k) > i_1]
  1939. else :
  1940. return [k for k in data_blocks if self.blocks.index(k) in xrange(i_1, i_2)]
  1941. def set_cyberk_blocks(self):
  1942. ck_blocks = list()
  1943. blocks = self.get_blocks_of_type('RCyberTag')
  1944. for block in blocks :
  1945. start = block.data_start
  1946. size = block.end + 1 - start
  1947. ck_blocks.append(DummyDataBlock(self, 'RCyberTag', start, size))
  1948. self.ck_blocks = ck_blocks
  1949. def episode_block(self, ep):
  1950. return self.episode_blocks[ep - 1]
  1951. @property
  1952. def n_episodes(self):
  1953. return len(self.episode_blocks)
  1954. def analog_index(self, episode):
  1955. """
  1956. Return indices relative to channels
  1957. used for analog signals.
  1958. """
  1959. block = self.episode_block(episode)
  1960. tag_mode = block.ep_block.tag_mode
  1961. an_index = np.where(np.array(block.ks_block.k_sampling) > 0)
  1962. if tag_mode == 2 :
  1963. an_index = an_index[:-1]
  1964. return an_index
  1965. def n_channels(self, episode):
  1966. """
  1967. Return the number of channels used
  1968. for analog signals but also events.
  1969. NB : in Elphy this 2 kinds of channels
  1970. are not differenciated.
  1971. """
  1972. block = self.episode_block(episode)
  1973. tag_mode = block.ep_block.tag_mode
  1974. n_channels = len(block.ks_block.k_sampling)
  1975. return n_channels if tag_mode != 2 else n_channels - 1
  1976. def n_tags(self, episode):
  1977. block = self.episode_block(episode)
  1978. tag_mode = block.ep_block.tag_mode
  1979. tag_map = {0:0, 1:2, 2:16, 3:16}
  1980. return tag_map.get(tag_mode, 0)
  1981. def n_events(self, episode):
  1982. """
  1983. Return the number of channels
  1984. dedicated to events.
  1985. """
  1986. block = self.episode_block(episode)
  1987. return block.ks_block.k_sampling.count(0)
  1988. def n_spiketrains(self, episode):
  1989. spk_blocks = [k for k in self.blocks if k.identifier == 'RSPK']
  1990. return spk_blocks[0].n_evt_channels if spk_blocks else 0
  1991. def sub_sampling(self, ep, ch):
  1992. """
  1993. Return the sub-sampling factor for
  1994. the specified episode and channel.
  1995. """
  1996. block = self.episode_block(ep)
  1997. return block.ks_block.k_sampling[ch - 1] if block.ks_block else 1
  1998. def aggregate_size(self, block, ep):
  1999. ag_count = self.aggregate_sample_count(block)
  2000. ag_size = 0
  2001. for ch in range(1, ag_count + 1) :
  2002. if (block.ks_block.k_sampling[ch - 1] != 0) :
  2003. ag_size += self.sample_size(ep, ch)
  2004. return ag_size
  2005. def n_samples(self, ep, ch):
  2006. block = self.episode_block(ep)
  2007. if not block.ep_block.continuous :
  2008. return block.ep_block.nbpt / self.sub_sampling(ep, ch)
  2009. else :
  2010. # for continuous case there isn't any place
  2011. # in the file that contains the number of
  2012. # samples unlike the episode case ...
  2013. data_blocks = self.get_data_blocks(ep)
  2014. total_size = np.sum([k.size for k in data_blocks])
  2015. # count the number of samples in an
  2016. # aggregate and compute its size in order
  2017. # to determine the size of an aggregate
  2018. ag_count = self.aggregate_sample_count(block)
  2019. ag_size = self.aggregate_size(block, ep)
  2020. n_ag = total_size / ag_size
  2021. # the number of samples is equal
  2022. # to the number of aggregates ...
  2023. n_samples = n_ag
  2024. n_chunks = total_size % ag_size
  2025. # ... but not when there exists
  2026. # a incomplete aggregate at the
  2027. # end of the file, consequently
  2028. # the preeceeding computed number
  2029. # of samples must be incremented
  2030. # by one only if the channel map
  2031. # to a sample in the last aggregate
  2032. # ... maybe this last part should be
  2033. # deleted because the n_chunks is always
  2034. # null in continuous mode
  2035. if n_chunks :
  2036. last_ag_size = total_size - n_ag * ag_count
  2037. size = 0
  2038. for i in range(0, ch) :
  2039. size += self.sample_size(ep, i + 1)
  2040. if size <= last_ag_size :
  2041. n_samples += 1
  2042. return n_samples
  2043. def sample_type(self, ep, ch):
  2044. block = self.episode_block(ep)
  2045. return block.kt_block.k_types[ch - 1] if block.kt_block else block.ep_block.tpData
  2046. def sampling_period(self, ep, ch):
  2047. block = self.episode_block(ep)
  2048. return block.ep_block.dX * self.sub_sampling(ep, ch)
  2049. def x_tag_scale_factors(self, ep):
  2050. block = self.episode_block(ep)
  2051. return ElphyScaleFactor(
  2052. block.ep_block.dX,
  2053. block.ep_block.X0
  2054. )
  2055. def x_scale_factors(self, ep, ch):
  2056. block = self.episode_block(ep)
  2057. return ElphyScaleFactor(
  2058. block.ep_block.dX * block.ks_block.k_sampling[ch - 1],
  2059. block.ep_block.X0,
  2060. )
  2061. def y_scale_factors(self, ep, ch):
  2062. block = self.episode_block(ep)
  2063. return ElphyScaleFactor(
  2064. block.ch_block.dY_ar[ch - 1],
  2065. block.ch_block.Y0_ar[ch - 1]
  2066. )
  2067. def x_unit(self, ep, ch):
  2068. block = self.episode_block(ep)
  2069. return block.ep_block.x_unit
  2070. def y_unit(self, ep, ch):
  2071. block = self.episode_block(ep)
  2072. return block.ch_block.y_units[ch - 1]
  2073. def tag_mode(self, ep):
  2074. block = self.episode_block(ep)
  2075. return block.ep_block.tag_mode
  2076. def tag_shift(self, ep):
  2077. block = self.episode_block(ep)
  2078. return block.ep_block.tag_shift
  2079. def get_channel_for_tags(self, ep):
  2080. block = self.episode_block(ep)
  2081. tag_mode = self.tag_mode(ep)
  2082. if tag_mode == 1 :
  2083. ks = np.array(block.ks_block.k_sampling)
  2084. mins = np.where(ks == ks.min())[0] + 1
  2085. return mins[0]
  2086. elif tag_mode == 2 :
  2087. return block.ep_block.n_channels
  2088. else :
  2089. return None
  2090. def aggregate_sample_count(self, block):
  2091. """
  2092. Return the number of sample in an aggregate.
  2093. """
  2094. # compute the least common multiple
  2095. # for channels having block.ks_block.k_sampling[ch] > 0
  2096. lcm0 = 1
  2097. for i in range(0, block.ep_block.n_channels) :
  2098. if block.ks_block.k_sampling[i] > 0 :
  2099. lcm0 = least_common_multiple(lcm0, block.ks_block.k_sampling[i])
  2100. # sum quotients lcm / KSampling
  2101. count = 0
  2102. for i in range(0, block.ep_block.n_channels) :
  2103. if block.ks_block.k_sampling[i] > 0 :
  2104. count += lcm0 / block.ks_block.k_sampling[i]
  2105. return count
  2106. def create_channel_mask(self, ep):
  2107. """
  2108. Return the minimal pattern of channel numbers
  2109. representing the succession of channels in the
  2110. multiplexed data. It is useful to do the mapping
  2111. between a sample stored in the file and its relative
  2112. channel.
  2113. NB : This function has been converted from the
  2114. 'TseqBlock.BuildMask' method of the file 'ElphyFormat.pas'
  2115. stored in Elphy source code.
  2116. """
  2117. block = self.episode_block(ep)
  2118. ag_count = self.aggregate_sample_count(block)
  2119. mask_ar = np.zeros(ag_count, dtype='i')
  2120. ag_size = 0
  2121. i = 0
  2122. k = 0
  2123. while k < ag_count :
  2124. for j in range(0, block.ep_block.n_channels) :
  2125. if (block.ks_block.k_sampling[j] != 0) and (i % block.ks_block.k_sampling[j] == 0) :
  2126. mask_ar[k] = j + 1
  2127. ag_size += self.sample_size(ep, j + 1)
  2128. k += 1
  2129. if k >= ag_count :
  2130. break
  2131. i += 1
  2132. return mask_ar
  2133. def get_signal(self, episode, channel):
  2134. block = self.episode_block(episode)
  2135. k_sampling = np.array(block.ks_block.k_sampling)
  2136. evt_channels = np.where(k_sampling == 0)[0]
  2137. if not channel in evt_channels :
  2138. return super(DAC2Layout, self).get_signal(episode, channel)
  2139. else :
  2140. k_sampling[channel - 1] = -1
  2141. return self.get_event(episode, channel, k_sampling)
  2142. def get_tag(self, episode, tag_channel):
  2143. """
  2144. Return a :class:`ElphyTag` which is a
  2145. descriptor of the specified event channel.
  2146. """
  2147. assert episode in range(1, self.n_episodes + 1)
  2148. # there are none, 2 or 16 tag
  2149. # channels depending on tag_mode
  2150. tag_mode = self.tag_mode(episode)
  2151. if tag_mode :
  2152. block = self.episode_block(episode)
  2153. x_unit = block.ep_block.x_unit
  2154. # verify the validity of the tag channel
  2155. if tag_mode == 1 :
  2156. assert tag_channel in range(1, 3), "Elphy format support only 2 tag channels for tag_mode == 1"
  2157. elif tag_mode == 2 :
  2158. assert tag_channel in range(1, 17), "Elphy format support only 16 tag channels for tag_mode == 2"
  2159. elif tag_mode == 3 :
  2160. assert tag_channel in range(1, 17), "Elphy format support only 16 tag channels for tag_mode == 3"
  2161. smp_period = block.ep_block.dX
  2162. smp_freq = 1.0 / smp_period
  2163. if tag_mode != 3 :
  2164. ch = self.get_channel_for_tags(episode)
  2165. n_samples = self.n_samples(episode, ch)
  2166. t_stop = (n_samples - 1) * smp_freq
  2167. else :
  2168. # get the max of n_samples multiplied by the sampling
  2169. # period done on every analog channels in order to avoid
  2170. # the selection of a channel without concrete signals
  2171. t_max = list()
  2172. for ch in self.analog_index(episode) :
  2173. n_samples = self.n_samples(episode, ch)
  2174. factors = self.x_scale_factors(episode, ch)
  2175. chtime = n_samples * factors.delta
  2176. t_max.append(chtime)
  2177. time_max = max(t_max)
  2178. # as (n_samples_tag - 1) * dX_tag
  2179. # and time_max = n_sample_tag * dX_tag
  2180. # it comes the following duration
  2181. t_stop = time_max - smp_period
  2182. return ElphyTag(self, episode, tag_channel, x_unit, smp_freq, 0, t_stop)
  2183. else :
  2184. return None
  2185. def get_event(self, ep, ch, marked_ks):
  2186. """
  2187. Return a :class:`ElphyEvent` which is a
  2188. descriptor of the specified event channel.
  2189. """
  2190. assert ep in range(1, self.n_episodes + 1)
  2191. assert ch in range(1, self.n_channels + 1)
  2192. # find the event channel number
  2193. evt_channel = np.where(marked_ks == -1)[0][0]
  2194. assert evt_channel in range(1, self.n_events(ep) + 1)
  2195. block = self.episode_block(ep)
  2196. ep_blocks = self.get_blocks_stored_in_episode(ep)
  2197. evt_blocks = [k for k in ep_blocks if k.identifier == 'REVT']
  2198. n_events = np.sum([k.n_events[evt_channel - 1] for k in evt_blocks], dtype=int)
  2199. x_unit = block.ep_block.x_unit
  2200. return ElphyEvent(self, ep, evt_channel, x_unit, n_events, ch_number=ch)
  2201. def load_encoded_events(self, episode, evt_channel, identifier):
  2202. """
  2203. Return times stored as a 4-bytes integer
  2204. in the specified event channel.
  2205. """
  2206. data_blocks = self.group_blocks_of_type(episode, identifier)
  2207. ep_blocks = self.get_blocks_stored_in_episode(episode)
  2208. evt_blocks = [k for k in ep_blocks if k.identifier == identifier]
  2209. #compute events on each channel
  2210. n_events = np.sum([k.n_events for k in evt_blocks], dtype=int, axis=0)
  2211. pre_events = np.sum(n_events[0:evt_channel - 1], dtype=int)
  2212. start = pre_events
  2213. end = start + n_events[evt_channel - 1]
  2214. expected_size = 4 * np.sum(n_events, dtype=int)
  2215. return self.load_bytes(data_blocks, dtype='<i4', start=start, end=end, expected_size=expected_size)
  2216. def load_encoded_spikes(self, episode, evt_channel, identifier):
  2217. """
  2218. Return times stored as a 4-bytes integer
  2219. in the specified spike channel.
  2220. NB: it is meant for Blackrock-type, having an additional byte for each event time as spike sorting label.
  2221. These additiona bytes are appended trailing the times.
  2222. """
  2223. # to load the requested spikes for the specified episode and event channel:
  2224. # get all the elphy blocks having as identifier 'RSPK' (or whatever)
  2225. all_rspk_blocks = [k for k in self.blocks if k.identifier == identifier]
  2226. rspk_block = all_rspk_blocks[episode-1]
  2227. # RDATA(h?dI) REVT(NbVeV:I, NbEv:256I ... spike data are 4byte integers
  2228. rspk_header = 4*( rspk_block.size - rspk_block.data_size-2 + len(rspk_block.n_events))
  2229. pre_events = np.sum(rspk_block.n_events[0:evt_channel-1], dtype=int, axis=0)
  2230. # the real start is after header, preceeding events (which are 4byte) and preceeding labels (1byte)
  2231. start = rspk_header + (4*pre_events) + pre_events
  2232. end = start + 4*rspk_block.n_events[evt_channel-1]
  2233. raw = self.load_bytes( [rspk_block], dtype='<i1', start=start, end=end, expected_size=rspk_block.size )
  2234. # re-encoding after reading byte by byte
  2235. res = np.frombuffer(raw[0:(4*rspk_block.n_events[evt_channel-1])], dtype='<i4')
  2236. res.sort() # sometimes timings are not sorted
  2237. #print "load_encoded_data() - spikes:",res
  2238. return res
  2239. def get_episode_name( self, episode ):
  2240. episode_name = "episode %s" % episode
  2241. names = [k for k in self.blocks if k.identifier == 'COM']
  2242. if len(names) > 0 :
  2243. name = names[episode-1]
  2244. start = name.size+1 - name.data_size+1
  2245. end = name.end - name.start+1
  2246. chars = self.load_bytes([name], dtype='uint8', start=start, end=end, expected_size=name.size ).tolist()
  2247. #print "chars[%s:%s]: %s" % (start,end,chars)
  2248. episode_name = ''.join([chr(k) for k in chars])
  2249. return episode_name
  2250. def get_event_data(self, episode, evt_channel):
  2251. """
  2252. Return times contained in the specified event channel.
  2253. This function is triggered when the 'times' property of
  2254. an :class:`ElphyEvent` descriptor instance is accessed.
  2255. """
  2256. times = self.load_encoded_events(episode, evt_channel, "REVT")
  2257. block = self.episode_block(episode)
  2258. return times * block.ep_block.dX / len(block.ks_block.k_sampling)
  2259. def get_spiketrain(self, episode, electrode_id):
  2260. """
  2261. Return a :class:`Spike` which is a
  2262. descriptor of the specified spike channel.
  2263. """
  2264. assert episode in range(1, self.n_episodes + 1)
  2265. assert electrode_id in range(1, self.n_spiketrains(episode) + 1)
  2266. # get some properties stored in the episode sub-block
  2267. block = self.episode_block(episode)
  2268. x_unit = block.ep_block.x_unit
  2269. x_unit_wf = getattr(block.ep_block, 'x_unit_wf', None)
  2270. y_unit_wf = getattr(block.ep_block, 'y_unit_wf', None)
  2271. # number of spikes in the entire episode
  2272. spk_blocks = [k for k in self.blocks if k.identifier == 'RSPK']
  2273. n_events = np.sum([k.n_events[electrode_id - 1] for k in spk_blocks], dtype=int)
  2274. # number of samples in a waveform
  2275. wf_sampling_frequency = 1.0 / block.ep_block.dX
  2276. wf_blocks = [k for k in self.blocks if k.identifier == 'RspkWave']
  2277. if wf_blocks :
  2278. wf_samples = wf_blocks[0].wavelength
  2279. t_start = wf_blocks[0].pre_trigger * block.ep_block.dX
  2280. else:
  2281. wf_samples = 0
  2282. t_start = 0
  2283. return ElphySpikeTrain(self, episode, electrode_id, x_unit, n_events, wf_sampling_frequency, wf_samples, x_unit_wf, y_unit_wf, t_start)
  2284. def get_spiketrain_data(self, episode, electrode_id):
  2285. """
  2286. Return times contained in the specified spike channel.
  2287. This function is triggered when the 'times' property of
  2288. an :class:`Spike` descriptor instance is accessed.
  2289. NB : The 'RSPK' block is not actually identical to the 'EVT' one,
  2290. because all units relative to a time are stored directly after all
  2291. event times, 1 byte for each. This function doesn't return these
  2292. units. But, they could be retrieved from the 'RspkWave' block with
  2293. the 'get_waveform_data function'
  2294. """
  2295. block = self.episode_block(episode)
  2296. times = self.load_encoded_spikes(episode, electrode_id, "RSPK")
  2297. return times * block.ep_block.dX
  2298. def load_encoded_waveforms(self, episode, electrode_id):
  2299. """
  2300. Return times on which waveforms are defined
  2301. and a numpy recarray containing all the data
  2302. stored in the RspkWave block.
  2303. """
  2304. # load data corresponding to the RspkWave block
  2305. identifier = "RspkWave"
  2306. data_blocks = self.group_blocks_of_type(episode, identifier)
  2307. databytes = self.load_bytes(data_blocks)
  2308. # select only data corresponding
  2309. # to the specified spk_channel
  2310. ep_blocks = self.get_blocks_stored_in_episode(episode)
  2311. wf_blocks = [k for k in ep_blocks if k.identifier == identifier]
  2312. wf_samples = wf_blocks[0].wavelength
  2313. events = np.sum([k.n_spikes for k in wf_blocks], dtype=int, axis=0)
  2314. n_events = events[electrode_id - 1]
  2315. pre_events = np.sum(events[0:electrode_id - 1], dtype=int)
  2316. start = pre_events
  2317. end = start + n_events
  2318. # data must be reshaped before
  2319. dtype = [
  2320. # the time of the spike arrival
  2321. ('elphy_time', 'u4', (1,)),
  2322. ('device_time', 'u4', (1,)),
  2323. # the identifier of the electrode
  2324. # would also be the 'trodalness'
  2325. # but this tetrode devices are not
  2326. # implemented in Elphy
  2327. ('channel_id', 'u2', (1,)),
  2328. # the 'category' of the waveform
  2329. ('unit_id', 'u1', (1,)),
  2330. #do not used
  2331. ('dummy', 'u1', (13,)),
  2332. # samples of the waveform
  2333. ('waveform', 'i2', (wf_samples,))
  2334. ]
  2335. x_start = wf_blocks[0].pre_trigger
  2336. x_stop = wf_samples - x_start
  2337. return np.arange(-x_start, x_stop), np.frombuffer(databytes, dtype=dtype)[start:end]
  2338. def get_waveform_data(self, episode, electrode_id):
  2339. """
  2340. Return waveforms corresponding to the specified
  2341. spike channel. This function is triggered when the
  2342. ``waveforms`` property of an :class:`Spike` descriptor
  2343. instance is accessed.
  2344. """
  2345. block = self.episode_block(episode)
  2346. times, databytes = self.load_encoded_waveforms(episode, electrode_id)
  2347. n_events, = databytes.shape
  2348. wf_samples = databytes['waveform'].shape[1]
  2349. dtype = [
  2350. ('time', float),
  2351. ('electrode_id', int),
  2352. ('unit_id', int),
  2353. ('waveform', float, (wf_samples, 2))
  2354. ]
  2355. data = np.empty(n_events, dtype=dtype)
  2356. data['electrode_id'] = databytes['channel_id'][:, 0]
  2357. data['unit_id'] = databytes['unit_id'][:, 0]
  2358. data['time'] = databytes['elphy_time'][:, 0] * block.ep_block.dX
  2359. data['waveform'][:, :, 0] = times * block.ep_block.dX
  2360. data['waveform'][:, :, 1] = databytes['waveform'] * block.ep_block.dY_wf + block.ep_block.Y0_wf
  2361. return data
  2362. def get_rspk_data(self, spk_channel):
  2363. """
  2364. Return times stored as a 4-bytes integer
  2365. in the specified event channel.
  2366. """
  2367. evt_blocks = self.get_blocks_of_type('RSPK')
  2368. #compute events on each channel
  2369. n_events = np.sum([k.n_events for k in evt_blocks], dtype=int, axis=0)
  2370. pre_events = np.sum(n_events[0:spk_channel], dtype=int) # sum of array values up to spk_channel-1!!!!
  2371. start = pre_events + (7 + len(n_events))# rspk header
  2372. end = start + n_events[spk_channel]
  2373. expected_size = 4 * np.sum(n_events, dtype=int) # constant
  2374. return self.load_bytes(evt_blocks, dtype='<i4', start=start, end=end, expected_size=expected_size)
  2375. # ---------------------------------------------------------
  2376. # factories.py
  2377. class LayoutFactory(object):
  2378. """
  2379. Generate base elements composing the layout of a file.
  2380. """
  2381. def __init__(self, elphy_file):
  2382. self.elphy_file = elphy_file
  2383. self.pattern = "\d{4}(\d+|\D)\D"
  2384. self.block_subclasses = dict()
  2385. @property
  2386. def file(self):
  2387. return self.elphy_file.file
  2388. def create_layout(self):
  2389. """
  2390. Return the actual :class:`ElphyLayout` subclass
  2391. instance used in an :class:`ElphyFile` object.
  2392. """
  2393. raise Exception('must be overloaded in a subclass')
  2394. def create_header(self, layout):
  2395. """
  2396. Return the actual :class:`Header` instance used
  2397. in an :class:`ElphyLayout` subclass object.
  2398. """
  2399. raise Exception('must be overloaded in a subclass')
  2400. def create_block(self, layout):
  2401. """
  2402. Return a :class:`Block` instance composing
  2403. the :class:`ElphyLayout` subclass instance.
  2404. """
  2405. raise Exception('must be overloaded in a subclass')
  2406. def create_sub_block(self, block, sub_offset):
  2407. """
  2408. Return a set of sub-blocks stored
  2409. in DAC2 objects format files.
  2410. """
  2411. self.file.seek(sub_offset)
  2412. sub_ident_size = read_from_char(self.file, 'B')
  2413. sub_identifier, = struct.unpack('<%ss' % sub_ident_size, self.file.read(sub_ident_size))
  2414. sub_data_size = read_from_char(self.file, 'H')
  2415. sub_data_offset = sub_offset + sub_ident_size + 3
  2416. size_format = "H"
  2417. if sub_data_size == 0xFFFF :
  2418. _ch = 'l'
  2419. sub_data_size = read_from_char(self.file, _ch)
  2420. size_format += "+%s" % (_ch)
  2421. sub_data_offset += 4
  2422. sub_size = len(sub_identifier) + 1 + type_dict[size_format] + sub_data_size
  2423. if sub_identifier == 'Ep' :
  2424. block_type = DAC2EpSubBlock
  2425. elif sub_identifier == 'Adc' :
  2426. block_type = DAC2AdcSubBlock
  2427. elif sub_identifier == 'Ksamp' :
  2428. block_type = DAC2KSampSubBlock
  2429. elif sub_identifier == 'Ktype' :
  2430. block_type = DAC2KTypeSubBlock
  2431. elif sub_identifier == 'USR' :
  2432. block_type = self.select_file_info_subclass()
  2433. else :
  2434. block_type = ElphyBlock
  2435. block = block_type(block.layout, sub_identifier, sub_offset, sub_size, size_format=size_format, parent_block=block)
  2436. self.file.seek(self.file.tell() + sub_data_size)
  2437. return block
  2438. def create_episode(self, block):
  2439. raise Exception('must be overloaded in a subclass')
  2440. def create_channel(self, block):
  2441. raise Exception('must be overloaded in a subclass')
  2442. def is_multistim(self, path):
  2443. """
  2444. Return a boolean telling if the
  2445. specified file is a multistim one.
  2446. """
  2447. match = re.search(self.pattern, path)
  2448. return hasattr(match, 'end') and path[match.end() - 1] in ['m', 'M']
  2449. def select_file_info_subclass(self):
  2450. """
  2451. Detect the type of a file from its nomenclature
  2452. and return its relative :class:`ClassicFileInfo` or
  2453. :class:`MultistimFileInfo` class. Useful to transparently
  2454. access to user file info stored in an Elphy file.
  2455. """
  2456. if not self.is_multistim(self.file.name) :
  2457. return ClassicFileInfo
  2458. else :
  2459. return MultistimFileInfo
  2460. def select_block_subclass(self, identifier):
  2461. return self.block_subclasses.get(identifier, ElphyBlock)
  2462. class Acquis1Factory(LayoutFactory):
  2463. """
  2464. Subclass of :class:`LayoutFactory` useful to
  2465. generate base elements composing the layout
  2466. of Acquis1 file format.
  2467. """
  2468. def __init__(self, elphy_file):
  2469. super(Acquis1Factory, self).__init__(elphy_file)
  2470. self.file.seek(16)
  2471. self.data_offset = read_from_char(self.file, 'h')
  2472. self.file.seek(0)
  2473. # the set of interesting blocks useful
  2474. # to retrieve data stored in a file
  2475. self.block_subclasses = {
  2476. "USER INFO" : self.select_file_info_subclass()
  2477. }
  2478. def create_layout(self):
  2479. return Acquis1Layout(self.elphy_file, self.data_offset)
  2480. def create_header(self, layout):
  2481. return Acquis1Header(layout)
  2482. def create_block(self, layout, offset):
  2483. self.file.seek(offset)
  2484. ident_size, identifier = struct.unpack('<B15s', self.file.read(16))
  2485. identifier = identifier[0:ident_size]
  2486. size = read_from_char(self.file, 'h')
  2487. block_type = self.select_block_subclass(identifier)
  2488. block = block_type(layout, identifier, offset, size, fixed_length=15, size_format='h')
  2489. self.file.seek(0)
  2490. return block
  2491. class DAC2GSFactory(LayoutFactory):
  2492. """
  2493. Subclass of :class:`LayoutFactory` useful to
  2494. generate base elements composing the layout
  2495. of DAC2/GS/2000 file format.
  2496. """
  2497. def __init__(self, elphy_file):
  2498. super(DAC2GSFactory, self).__init__(elphy_file)
  2499. self.file.seek(16)
  2500. self.data_offset = read_from_char(self.file, 'i')
  2501. self.file.seek(0)
  2502. # the set of interesting blocks useful
  2503. # to retrieve data stored in a file
  2504. self.block_subclasses = {
  2505. "USER INFO" : self.select_file_info_subclass(),
  2506. "DAC2SEQ" : DAC2GSEpisodeBlock,
  2507. 'MAIN' : DAC2GSMainBlock,
  2508. }
  2509. def create_layout(self):
  2510. return DAC2GSLayout(self.elphy_file, self.data_offset)
  2511. def create_header(self, layout):
  2512. return DAC2GSHeader(layout)
  2513. def create_block(self, layout, offset):
  2514. self.file.seek(offset)
  2515. ident_size, identifier = struct.unpack('<B15s', self.file.read(16))
  2516. # block title size is 7 or 15 bytes
  2517. # 7 is for sequence blocs
  2518. if identifier.startswith('DAC2SEQ') :
  2519. self.file.seek(self.file.tell() - 8)
  2520. length = 7
  2521. else :
  2522. length = 15
  2523. identifier = identifier[0:ident_size]
  2524. size = read_from_char(self.file, 'i')
  2525. block_type = self.select_block_subclass(identifier)
  2526. block = block_type(layout, identifier, offset, size, fixed_length=length, size_format='i')
  2527. self.file.seek(0)
  2528. return block
  2529. class DAC2Factory(LayoutFactory):
  2530. """
  2531. Subclass of :class:`LayoutFactory` useful to
  2532. generate base elements composing the layout
  2533. of DAC2 objects file format.
  2534. """
  2535. def __init__(self, elphy_file):
  2536. super(DAC2Factory, self).__init__(elphy_file)
  2537. # the set of interesting blocks useful
  2538. # to retrieve data stored in a file
  2539. self.block_subclasses = {
  2540. "B_Ep" : DAC2EpisodeBlock,
  2541. "RDATA" : DAC2RDataBlock,
  2542. "RCyberTag" : DAC2CyberTagBlock,
  2543. "REVT" : DAC2EventBlock,
  2544. "RSPK" : DAC2SpikeBlock,
  2545. "RspkWave" : DAC2WaveFormBlock
  2546. }
  2547. def create_layout(self):
  2548. return DAC2Layout(self.elphy_file)
  2549. def create_header(self, layout):
  2550. return DAC2Header(layout)
  2551. def create_block(self, layout, offset):
  2552. self.file.seek(offset)
  2553. size = read_from_char(self.file, 'l')
  2554. ident_size = read_from_char(self.file, 'B')
  2555. identifier, = struct.unpack('<%ss' % ident_size, self.file.read(ident_size))
  2556. block_type = self.select_block_subclass(identifier)
  2557. block = block_type(layout, identifier, offset, size, size_format='l')
  2558. self.file.seek(0)
  2559. return block
  2560. #caching all available layout factories
  2561. factories = {
  2562. "ACQUIS1/GS/1991" : Acquis1Factory,
  2563. "DAC2/GS/2000" : DAC2GSFactory,
  2564. "DAC2 objects" : DAC2Factory
  2565. }
  2566. # --------------------------------------------------------
  2567. # ELPHY FILE
  2568. """
  2569. Classes useful to retrieve data from the
  2570. three major Elphy formats, i.e : Acquis1, DAC2/GS/2000, DAC2 objects.
  2571. The :class:`ElphyFile` class is useful to access raw data and user info
  2572. that stores protocol metadata. Internally, It uses a subclass :class:`ElphyLayout`
  2573. to handle each kind of file format : :class:`Acquis1Layout`, :class:`DAC2GSLayout`
  2574. and :class:`DAC2Layout`.
  2575. These layouts decompose the file structure into several blocks of data, inheriting
  2576. from the :class:`BaseBlock`, corresponding for example to the header of the file,
  2577. the user info, the raw data, the episode or channel properties. Each subclass of
  2578. :class:`BaseBlock` map to a file chunk and is responsible to store metadata contained
  2579. in this chunk. These metadata could be also useful to reconstruct raw data.
  2580. Consequently, when an :class:`ElphyLayout` layout is requested by its relative
  2581. :class:`ElphyFile`, It iterates through :class:`BaseBlock` objects to retrieve
  2582. asked data.
  2583. NB : The reader is not able to read Acquis1 and DAC2/GS/2000 event channels.
  2584. """
  2585. class ElphyFile(object):
  2586. """
  2587. A convenient class useful to read Elphy files.
  2588. It acts like a file reader that wraps up a python
  2589. file opened in 'rb' mode in order to retrieve
  2590. directly from an Elphy file raw data and metadata
  2591. relative to protocols.
  2592. ``path`` : the path of the elphy file.
  2593. ``file`` : the python file object that iterates
  2594. through the elphy file.
  2595. ``file_size`` : the size of the elphy file on the
  2596. hard disk drive.
  2597. ``nomenclature`` : the label that identifies the
  2598. kind of elphy format, i.e. 'Acquis1', 'DAC2/GS/2000',
  2599. 'DAC2 objects'.
  2600. ``factory`` : the :class:`LayoutFactory` object which
  2601. generates the base component of the elphy file layout.
  2602. ``layout`` : the :class:`ElphyLayout` object which
  2603. decomposes the file structure into several blocks of
  2604. data (:class:`BaseBlock` objects). The :class:`ElphyFile`
  2605. object do requests to this layout which iterates through
  2606. this blocks before returning asked data.
  2607. ``protocol`` : the acquisition protocol which has generated
  2608. the file.
  2609. ``version`` : the variant of the acquisition protocol.
  2610. NB : An elphy file could store several kind of data :
  2611. (1) 'User defined' metadata which are stored in a block
  2612. called 'USER INFO' ('Acquis1' and 'DAC2/GS/2000') or 'USR'
  2613. ('DAC2 objects') of the ``layout``. They could be used for
  2614. example to describe stimulation parameters.
  2615. (2) Raw data acquired on separate analog channels. Data
  2616. coming from each channel are multiplexed in blocks dedicated
  2617. to raw data storage :
  2618. - For Acquis1 format, raw data are stored directly
  2619. after the file header.
  2620. - For DAC2/GS/2000, in continuous mode they are stored
  2621. after all blocks composing the file else they are stored
  2622. in a 'DAC2SEQ' block.
  2623. - For 'DAC2 objects' they are stored in 'RDATA' blocks.
  2624. In continuous mode raw data could be spread other multiple
  2625. 'RDATA' blocks. Whereas in episode mode there is a single
  2626. 'RDATA' block for each episode.
  2627. These raw data are placed under the 'channels' node of a
  2628. TDataFile object listed in Elphy's "Inspect" tool.
  2629. (3) ElphyEvents dedicated to threshold detection in analog
  2630. channels. ElphyEvents are only available for 'DAC2 objects'
  2631. format. For 'Acquis1' and 'DAC2/GS/2000' these events are
  2632. in fact stored in another kind of file format called
  2633. 'event' format with the '.evt' extension which is opened
  2634. by Elphy as same time as the '.dat' file. This 'event'
  2635. format is not yet implemented because it seems that it
  2636. was not really used.
  2637. These events are also placed under the 'channels' node
  2638. of a TDataFile object in Elphy's "Inspect" tool.
  2639. (4) ElphyTags that appeared after 'DAC2/GS/2000' release. They
  2640. are also present in 'DAC2 objects' format. Each, tag occupies
  2641. a channel called 'tag' channel. Their encoding depends on the
  2642. kind of acquisition card :
  2643. - For 'digidata' cards (``tag_mode``=1) and if tags are acquired,
  2644. they are directly encoded in 2 (digidata 1322) or 4 (digidata 1200)
  2645. significant bits of 16-bits samples coming from an analog channel.
  2646. In all cases they are only 2 bits encoding the tag channels. The
  2647. sample value could be encoded on 16, 14 or 12 bits and retrieved by
  2648. applying a shift equal to ``tag_shift`` to the right.
  2649. - For ITC cards (``tag_mode``=2), tags are transmitted by a channel
  2650. fully dedicated to 'tag channels' providing 16-bits samples. In this
  2651. case, each bit corresponds to a 'tag channel'.
  2652. - For Blackrock/Cyberkinetics devices (``tag_mode``=3), tags are also
  2653. transmitted by a channel fully dedicated to tags, but the difference is
  2654. that only transitions are stored in 'RCyberTag' blocks. This case in only
  2655. available in 'DAC2 objects' format.
  2656. These tags are placed under the 'Vtags' node of a TDataFile
  2657. object in Elphy's "Inspect" tool.
  2658. (5) Spiketrains coming from an electrode of a Blackrock/Cyberkinetics
  2659. multi-electrode device. These data are only available in 'DAC2 objects'
  2660. format.
  2661. These spiketrains are placed under the 'Vspk' node of a TDataFile
  2662. object in Elphy's "Inspect" tool.
  2663. (6) Waveforms relative to each time of a spiketrain. These data are only
  2664. available in 'DAC2 objects' format. These waveforms are placed under the
  2665. 'Wspk' node of a TDataFile object in Elphy's "Inspect" tool.
  2666. """
  2667. def __init__(self, file_path) :
  2668. self.path = file_path
  2669. self.folder, self.filename = path.split(self.path)
  2670. self.file = None
  2671. self.file_size = None
  2672. self.nomenclature = None
  2673. self.factory = None
  2674. self.layout = None
  2675. # writing support
  2676. self.header_size = None
  2677. def __del__(self):
  2678. """
  2679. Trigger closing of the file.
  2680. """
  2681. self.close()
  2682. # super(ElphyFile, self).__del__()
  2683. def open(self):
  2684. """
  2685. Setup the internal structure.
  2686. NB : Call this function before
  2687. extracting data from a file.
  2688. """
  2689. if self.file :
  2690. self.file.close()
  2691. try :
  2692. self.file = open(self.path, 'rb')
  2693. except Exception as e:
  2694. raise Exception("python couldn't open file %s : %s" % (self.path, e))
  2695. self.file_size = path.getsize(self.file.name)
  2696. self.creation_date = datetime.fromtimestamp(path.getctime(self.file.name))
  2697. self.modification_date = datetime.fromtimestamp(path.getmtime(self.file.name))
  2698. self.nomenclature = self.get_nomenclature()
  2699. self.factory = self.get_factory()
  2700. self.layout = self.create_layout()
  2701. def close(self):
  2702. """
  2703. Close the file.
  2704. """
  2705. if self.file :
  2706. self.file.close()
  2707. def get_nomenclature(self):
  2708. """
  2709. Return the title of the file header
  2710. giving the actual file format. This
  2711. title is encoded as a pascal string
  2712. containing 15 characters and stored
  2713. as 16 bytes of binary data.
  2714. """
  2715. self.file.seek(0)
  2716. length, title = struct.unpack('<B15s', self.file.read(16))
  2717. self.file.seek(0)
  2718. title = title[0:length]
  2719. if not title in factories :
  2720. title = "format is not implemented ('%s' not in %s)" % (title, str(factories.keys()))
  2721. return title
  2722. def set_nomenclature(self):
  2723. """
  2724. As in get_nomenclature, but set the title of the file header
  2725. in the file, encoded as a pascal string containing
  2726. 15 characters and stored as 16 bytes of binary data.
  2727. """
  2728. self.file.seek(0)
  2729. title = 'DAC2 objects'
  2730. st = struct.Struct( '<B15sH' )
  2731. header_rec = [len(title), title, 18] # constant header
  2732. header_chr = st.pack( *header_rec )
  2733. self.header_size = len( header_chr )
  2734. self.file.write( header_chr )
  2735. def get_factory(self):
  2736. """
  2737. Return a subclass of :class:`LayoutFactory`
  2738. useful to build the file layout depending
  2739. on header title.
  2740. """
  2741. return factories[self.nomenclature](self)
  2742. def write(self, data):
  2743. """
  2744. Assume the blocks are already filled.
  2745. It is able to write several types of block: B_Ep, RDATA, ...
  2746. and subBlock: Adc, Ksamp, Ktype, dataRecord, ...
  2747. In the following shape:
  2748. B_Ep
  2749. |_ Ep
  2750. |_ Adc
  2751. |_ Adc
  2752. |_ ...
  2753. |_ Ktype
  2754. RDATA
  2755. |_ dataRecord+data
  2756. """
  2757. # close if open and reopen for writing
  2758. if self.file :
  2759. self.file.close()
  2760. try :
  2761. self.file = open(self.path, 'wb')
  2762. except Exception as e:
  2763. raise Exception("python couldn't open file %s : %s" % (self.path, e))
  2764. self.file_size = 0
  2765. self.creation_date = datetime.now()
  2766. self.modification_date = datetime.now()
  2767. self.set_nomenclature()
  2768. # then call ElphyFile writing routines to write the serialized string
  2769. self.file.write( data ) # actual writing
  2770. # close file
  2771. self.close()
  2772. def create_layout(self):
  2773. """
  2774. Build the :class:`Layout` object corresponding
  2775. to the file format and configure properties of
  2776. itself and then its blocks and sub-blocks.
  2777. NB : this function must be called before all kind
  2778. of requests on the file because it is used also to setup
  2779. the internal properties of the :class:`ElphyLayout`
  2780. object or some :class:`BaseBlock` objects. Consequently,
  2781. executing some function corresponding to a request on
  2782. the file has many chances to lead to bad results.
  2783. """
  2784. # create the layout
  2785. layout = self.factory.create_layout()
  2786. # create the header block and
  2787. # add it to the list of blocks
  2788. header = self.factory.create_header(layout)
  2789. layout.add_block(header)
  2790. # set the position of the cursor
  2791. # in order to be after the header
  2792. # block and then compute its last
  2793. # valid position to know when stop
  2794. # the iteration through the file
  2795. offset = header.size
  2796. offset_stop = layout.get_blocks_end()
  2797. # in continuous mode DAC2/GS/2000 raw data are not stored
  2798. # into several DAC2SEQ blocks, they are stored after all
  2799. # available blocks, that's why it is necessary to limit the
  2800. # loop to data_offset when it is a DAC2/GS/2000 format
  2801. is_continuous = False
  2802. detect_continuous = False
  2803. detect_main = False
  2804. while (offset < offset_stop) and not (is_continuous and (offset >= layout.data_offset)) :
  2805. block = self.factory.create_block(layout, offset)
  2806. # create the sub blocks if it is DAC2 objects format
  2807. # this is only done for B_Ep and B_Finfo blocks for
  2808. # DAC2 objects format, maybe it could be useful to
  2809. # spread this to other block types.
  2810. #if isinstance(header, DAC2Header) and (block.identifier in ['B_Ep']) :
  2811. if isinstance(header, DAC2Header) and (block.identifier in ['B_Ep', 'B_Finfo']) :
  2812. sub_offset = block.data_offset
  2813. while sub_offset < block.start + block.size :
  2814. sub_block = self.factory.create_sub_block(block, sub_offset)
  2815. block.add_sub_block(sub_block)
  2816. sub_offset += sub_block.size
  2817. # set up some properties of some DAC2Layout sub-blocks
  2818. if isinstance(sub_block, (DAC2EpSubBlock, DAC2AdcSubBlock, DAC2KSampSubBlock, DAC2KTypeSubBlock)) :
  2819. block.set_episode_block()
  2820. block.set_channel_block()
  2821. block.set_sub_sampling_block()
  2822. block.set_sample_size_block()
  2823. # SpikeTrain
  2824. #if isinstance(header, DAC2Header) and (block.identifier in ['RSPK']) :
  2825. #print "\nElphyFile.create_layout() - RSPK"
  2826. #print "ElphyFile.create_layout() - n_events",block.n_events
  2827. #print "ElphyFile.create_layout() - n_evt_channels",block.n_evt_channels
  2828. layout.add_block(block)
  2829. offset += block.size
  2830. # set up as soon as possible the shortcut
  2831. # to the main block of a DAC2GSLayout
  2832. if not detect_main and isinstance(layout, DAC2GSLayout) and isinstance(block, DAC2GSMainBlock) :
  2833. layout.set_main_block()
  2834. detect_main = True
  2835. # detect if the file is continuous when
  2836. # the 'MAIN' block has been parsed
  2837. if not detect_continuous :
  2838. is_continuous = isinstance(header, DAC2GSHeader) and layout.is_continuous()
  2839. # set up the shortcut to blocks corresponding
  2840. # to episodes, only available for DAC2Layout
  2841. # and also DAC2GSLayout if not continuous
  2842. if isinstance(layout, DAC2Layout) or (isinstance(layout, DAC2GSLayout) and not layout.is_continuous()) :
  2843. layout.set_episode_blocks()
  2844. layout.set_data_blocks()
  2845. # finally set up the user info block of the layout
  2846. layout.set_info_block()
  2847. self.file.seek(0)
  2848. return layout
  2849. def is_continuous(self):
  2850. return self.layout.is_continuous()
  2851. @property
  2852. def n_episodes(self):
  2853. """
  2854. Return the number of recording sequences.
  2855. """
  2856. return self.layout.n_episodes
  2857. def n_channels(self, episode):
  2858. """
  2859. Return the number of recording
  2860. channels involved in data acquisition
  2861. and relative to the specified episode :
  2862. ``episode`` : the recording sequence identifier.
  2863. """
  2864. return self.layout.n_channels(episode)
  2865. def n_tags(self, episode):
  2866. """
  2867. Return the number of tag channels
  2868. relative to the specified episode :
  2869. ``episode`` : the recording sequence identifier.
  2870. """
  2871. return self.layout.n_tags(episode)
  2872. def n_events(self, episode):
  2873. """
  2874. Return the number of event channels
  2875. relative to the specified episode :
  2876. ``episode`` : the recording sequence identifier.
  2877. """
  2878. return self.layout.n_events(episode)
  2879. def n_spiketrains(self, episode):
  2880. """
  2881. Return the number of event channels
  2882. relative to the specified episode :
  2883. ``episode`` : the recording sequence identifier.
  2884. """
  2885. return self.layout.n_spiketrains(episode)
  2886. def n_waveforms(self, episode):
  2887. """
  2888. Return the number of waveform channels :
  2889. """
  2890. return self.layout.n_waveforms(episode)
  2891. def get_signal(self, episode, channel):
  2892. """
  2893. Return the signal or event descriptor relative
  2894. to the specified episode and channel :
  2895. ``episode`` : the recording sequence identifier.
  2896. ``channel`` : the analog channel identifier.
  2897. NB : For 'DAC2 objects' format, it could
  2898. be also used to retrieve events.
  2899. """
  2900. return self.layout.get_signal(episode, channel)
  2901. def get_tag(self, episode, tag_channel):
  2902. """
  2903. Return the tag descriptor relative to
  2904. the specified episode and tag channel :
  2905. ``episode`` : the recording sequence identifier.
  2906. ``tag_channel`` : the tag channel identifier.
  2907. NB : There isn't any tag channels for
  2908. 'Acquis1' format. ElphyTag channels appeared
  2909. after 'DAC2/GS/2000' release. They are
  2910. also present in 'DAC2 objects' format.
  2911. """
  2912. return self.layout.get_tag(episode, tag_channel)
  2913. def get_event(self, episode, evt_channel):
  2914. """
  2915. Return the event relative the
  2916. specified episode and event channel.
  2917. `episode`` : the recording sequence identifier.
  2918. ``tag_channel`` : the tag channel identifier.
  2919. """
  2920. return self.layout.get_event(episode, evt_channel)
  2921. def get_spiketrain(self, episode, electrode_id):
  2922. """
  2923. Return the spiketrain relative to the
  2924. specified episode and electrode_id.
  2925. ``episode`` : the recording sequence identifier.
  2926. ``electrode_id`` : the identifier of the electrode providing the spiketrain.
  2927. NB : Available only for 'DAC2 objects' format.
  2928. This descriptor can return the times of a spiketrain
  2929. and waveforms relative to each of these times.
  2930. """
  2931. return self.layout.get_spiketrain(episode, electrode_id)
  2932. @property
  2933. def comments(self):
  2934. raise NotImplementedError()
  2935. def get_user_file_info(self):
  2936. """
  2937. Return user defined file metadata.
  2938. """
  2939. if not self.layout.info_block :
  2940. return dict()
  2941. else :
  2942. return self.layout.info_block.get_user_file_info()
  2943. @property
  2944. def episode_info(self, ep_number):
  2945. raise NotImplementedError()
  2946. def get_signals(self):
  2947. """
  2948. Get all available analog or event channels stored into an Elphy file.
  2949. """
  2950. signals = list()
  2951. for ep in range(1, self.n_episodes + 1) :
  2952. for ch in range(1, self.n_channels(ep) + 1) :
  2953. signal = self.get_signal(ep, ch)
  2954. signals.append(signal)
  2955. return signals
  2956. def get_tags(self):
  2957. """
  2958. Get all available tag channels stored into an Elphy file.
  2959. """
  2960. tags = list()
  2961. for ep in range(1, self.n_episodes + 1) :
  2962. for tg in range(1, self.n_tags(ep) + 1) :
  2963. tag = self.get_tag(ep, tg)
  2964. tags.append(tag)
  2965. return tags
  2966. def get_spiketrains(self):
  2967. """
  2968. Get all available spiketrains stored into an Elphy file.
  2969. """
  2970. spiketrains = list()
  2971. for ep in range(1, self.n_episodes + 1) :
  2972. for ch in range(1, self.n_spiketrains(ep) + 1) :
  2973. spiketrain = self.get_spiketrain(ep, ch)
  2974. spiketrains.append(spiketrain)
  2975. return spiketrains
  2976. def get_rspk_spiketrains(self):
  2977. """
  2978. Get all available spiketrains stored into an Elphy file.
  2979. """
  2980. spiketrains = list()
  2981. spk_blocks = self.layout.get_blocks_of_type('RSPK')
  2982. for bl in spk_blocks :
  2983. #print "ElphyFile.get_spiketrains() - identifier:",bl.identifier
  2984. for ch in range(0,bl.n_evt_channels) :
  2985. spiketrain = self.layout.get_rspk_data(ch)
  2986. spiketrains.append(spiketrain)
  2987. return spiketrains
  2988. def get_names( self ) :
  2989. com_blocks = list()
  2990. com_blocks = self.layout.get_blocks_of_type('COM')
  2991. return com_blocks
  2992. # --------------------------------------------------------
  2993. class ElphyIO(BaseIO):
  2994. """
  2995. Class for reading from and writing to an Elphy file.
  2996. It enables reading:
  2997. - :class:`Block`
  2998. - :class:`Segment`
  2999. - :class:`RecordingChannel`
  3000. - :class:`ChannelIndex`
  3001. - :class:`Event`
  3002. - :class:`SpikeTrain`
  3003. Usage:
  3004. >>> from neo import io
  3005. >>> r = io.ElphyIO(filename='ElphyExample.DAT')
  3006. >>> seg = r.read_block(lazy=False, cascade=True)
  3007. >>> print(seg.analogsignals) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  3008. >>> print(seg.spiketrains) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  3009. >>> print(seg.events) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  3010. >>> print(anasig._data_description)
  3011. >>> anasig = r.read_analogsignal(lazy=False, cascade=False)
  3012. >>> bl = Block()
  3013. >>> # creating segments, their contents and append to bl
  3014. >>> r.write_block( bl )
  3015. """
  3016. is_readable = True # This class can read data
  3017. is_writable = False # This class can write data
  3018. # This class is able to directly or indirectly handle the following objects
  3019. supported_objects = [ Block, Segment, AnalogSignal, SpikeTrain ]
  3020. # This class can return a Block
  3021. readable_objects = [ Block ]
  3022. # This class is not able to write objects
  3023. writeable_objects = [ ]
  3024. has_header = False
  3025. is_streameable = False
  3026. # This is for GUI stuff : a definition for parameters when reading.
  3027. # This dict should be keyed by object (`Block`). Each entry is a list
  3028. # of tuple. The first entry in each tuple is the parameter name. The
  3029. # second entry is a dict with keys 'value' (for default value),
  3030. # and 'label' (for a descriptive name).
  3031. # Note that if the highest-level object requires parameters,
  3032. # common_io_test will be skipped.
  3033. read_params = {
  3034. }
  3035. # do not supported write so no GUI stuff
  3036. write_params = {
  3037. }
  3038. name = 'Elphy IO'
  3039. extensions = [ 'DAT' ]
  3040. # mode can be 'file' or 'dir' or 'fake' or 'database'
  3041. mode = 'file'
  3042. # internal serialized representation of neo data
  3043. serialized = None
  3044. def __init__(self , filename = None) :
  3045. """
  3046. Arguments:
  3047. filename : the filename to read
  3048. """
  3049. BaseIO.__init__(self)
  3050. self.filename = filename
  3051. self.elphy_file = ElphyFile(self.filename)
  3052. def read_block(self,
  3053. # the 2 first key arguments are imposed by neo.io API
  3054. lazy = False,
  3055. cascade = True
  3056. ):
  3057. """
  3058. Return :class:`Block` filled or not depending on 'cascade' parameter.
  3059. Parameters:
  3060. lazy : postpone actual reading of the file.
  3061. cascade : normally you want this True, otherwise method will only ready Block label.
  3062. """
  3063. # basic
  3064. block = Block(name=None)
  3065. # laziness
  3066. if lazy:
  3067. return block
  3068. else:
  3069. # get analog and tag channels
  3070. try :
  3071. self.elphy_file.open()
  3072. except Exception as e:
  3073. self.elphy_file.close()
  3074. raise Exception("cannot open file %s : %s" % (self.filename, e))
  3075. # cascading
  3076. #print "\n\n==========================================\n"
  3077. #print "read_block() - n_episodes:",self.elphy_file.n_episodes
  3078. if cascade:
  3079. # create a segment containing all analog,
  3080. # tag and event channels for the episode
  3081. if self.elphy_file.n_episodes == None :
  3082. print("File '%s' appears to have no episodes" % (self.filename))
  3083. return block
  3084. for episode in range(1, self.elphy_file.n_episodes+1) :
  3085. segment = self.read_segment(episode)
  3086. segment.block = block
  3087. block.segments.append(segment)
  3088. # close file
  3089. self.elphy_file.close()
  3090. # result
  3091. return block
  3092. def write_block( self, block ):
  3093. """
  3094. Write a given Neo Block to an Elphy file, its structure being, for example:
  3095. Neo -> Elphy
  3096. --------------------------------------------------------------
  3097. Block File
  3098. Segment Episode Block (B_Ep)
  3099. AnalogSignalArray Episode Descriptor (Ep + Adc + Ksamp + Ktype)
  3100. multichannel RDATA (with a ChannelMask multiplexing channels)
  3101. 2D NumPy Array
  3102. ...
  3103. AnalogSignalArray
  3104. AnalogSignal
  3105. AnalogSignal
  3106. ...
  3107. ...
  3108. SpikeTrain Event Block (RSPK)
  3109. SpikeTrain
  3110. ...
  3111. Arguments::
  3112. block: the block to be saved
  3113. """
  3114. # Serialize Neo structure into Elphy file
  3115. # each analog signal will be serialized as elphy Episode Block (with its subblocks)
  3116. # then all spiketrains will be serialized into an Rspk Block (an Event Block with addons).
  3117. # Serialize (and size) all Neo structures before writing them to file
  3118. # Since to write each Elphy Block is required to know in advance its size,
  3119. # which includes that of its subblocks, it is necessary to
  3120. # serialize first the lowest structures.
  3121. # Iterate over block structures
  3122. elphy_limit = 256
  3123. All = ''
  3124. #print "\n\n--------------------------------------------\n"
  3125. #print "write_block() - n_segments:",len(block.segments)
  3126. for seg in block.segments:
  3127. analogsignals = 0 # init
  3128. nbchan = 0
  3129. nbpt = 0
  3130. chls = 0
  3131. Dxu = 1e-8 #0.0000001
  3132. Rxu = 1e+8 #10000000.0
  3133. X0uSpk = 0.0
  3134. CyberTime = 0.0
  3135. aa_units = []
  3136. NbEv = []
  3137. serialized_analog_data = ''
  3138. serialized_spike_data = ''
  3139. # AnalogSignals
  3140. # Neo signalarrays are 2D numpy array where each row is an array of samples for a channel:
  3141. # signalarray A = [[ 1, 2, 3, 4 ],
  3142. # [ 5, 6, 7, 8 ]]
  3143. # signalarray B = [[ 9, 10, 11, 12 ],
  3144. # [ 13, 14, 15, 16 ]]
  3145. # Neo Segments can have more than one signalarray.
  3146. # To be converted in Elphy analog channels they need to be all in a 2D array, not in several 2D arrays.
  3147. # Concatenate all analogsignalarrays into one and then flatten it.
  3148. # Elphy RDATA blocks contain Fortran styled samples:
  3149. # 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16
  3150. # AnalogSignalArrays -> analogsignals
  3151. # get the first to have analogsignals with the right shape
  3152. # Annotations for analogsignals array come as a list of int being source ids
  3153. # here, put each source id on a separate dict entry in order to have a matching afterwards
  3154. idx = 0
  3155. annotations = dict( )
  3156. # get all the others
  3157. #print "write_block() - n_analogsignals:",len(seg.analogsignals)
  3158. #print "write_block() - n_analogsignalarrays:",len(seg.analogsignalarrays)
  3159. for asigar in seg.analogsignalarrays :
  3160. idx,annotations = self.get_annotations_dict( annotations, "analogsignal", asigar.annotations.items(), asigar.name, idx )
  3161. # array structure
  3162. _,chls = asigar.shape
  3163. # units
  3164. for _ in range(chls) :
  3165. aa_units.append( asigar.units )
  3166. Dxu = asigar.sampling_period
  3167. Rxu = asigar.sampling_rate
  3168. if isinstance(analogsignals, np.ndarray) :
  3169. analogsignals = np.hstack( (analogsignals,asigar) )
  3170. else :
  3171. analogsignals = asigar # first time
  3172. # collect and reshape all analogsignals
  3173. if isinstance(analogsignals, np.ndarray) :
  3174. # transpose matrix since in Neo channels are column-wise while in Elphy are row-wise
  3175. analogsignals = analogsignals.T
  3176. # get dimensions
  3177. nbchan,nbpt = analogsignals.shape
  3178. # serialize AnalogSignal
  3179. analog_data_fmt = '<' + str(analogsignals.size) + 'f'
  3180. # serialized flattened numpy channels in 'F'ortran style
  3181. analog_data_64 = analogsignals.flatten('F')
  3182. # elphy normally uses float32 values (for performance reasons)
  3183. analog_data = np.array( analog_data_64, dtype=np.float32 )
  3184. serialized_analog_data += struct.pack( analog_data_fmt, *analog_data )
  3185. # SpikeTrains
  3186. # Neo spiketrains are stored as a one-dimensional array of times
  3187. # [ 0.11, 1.23, 2.34, 3.45, 4.56, 5.67, 6.78, 7.89 ... ]
  3188. # These are converted into Elphy Rspk Block which will contain all of them
  3189. # RDATA + NbVeV:integer for the number of channels (spiketrains)
  3190. # + NbEv:integer[] for the number of event per channel
  3191. # followed by the actual arrays of integer containing spike times
  3192. #spiketrains = seg.spiketrains
  3193. # ... but consider elphy loading limitation:
  3194. NbVeV = len( seg.spiketrains )
  3195. #print "write_block() - n_spiketrains:",NbVeV
  3196. if len(seg.spiketrains) > elphy_limit :
  3197. NbVeV = elphy_limit
  3198. # serialize format
  3199. spiketrain_data_fmt = '<'
  3200. spiketrains = []
  3201. for idx,train in enumerate(seg.spiketrains[:NbVeV]) :
  3202. #print "write_block() - train.size:", train.size,idx
  3203. #print "write_block() - train:", train
  3204. fake,annotations = self.get_annotations_dict( annotations,"spiketrain", train.annotations.items(), '', idx )
  3205. #annotations.update( dict( [("spiketrain-"+str(idx),train.annotations['source_id'])] ) )
  3206. #print "write_block() - train[%s].annotation['source_id']:%s" % (idx,train.annotations['source_id'])
  3207. # total number of events format + blackrock sorting mark (0 for neo)
  3208. spiketrain_data_fmt += str(train.size) + "i" + str(train.size) + "B"
  3209. # get starting time
  3210. X0uSpk = train.t_start.item()
  3211. CyberTime = train.t_stop.item()
  3212. # count number of events per train
  3213. NbEv.append( train.size )
  3214. # multiply by sampling period
  3215. train = train * Rxu
  3216. # all flattened spike train
  3217. # blackrock acquisition card also adds a byte for each event to sort it
  3218. spiketrains.extend( [spike.item() for spike in train] + [0 for _ in range(train.size)])
  3219. # Annotations
  3220. #print annotations
  3221. # using DBrecord elphy block, they will be available as values in elphy environment
  3222. # separate keys and values in two separate serialized strings
  3223. ST_sub = ''
  3224. st_fmt = ''
  3225. st_data = []
  3226. BUF_sub = ''
  3227. serialized_ST_data = ''
  3228. serialized_BUF_data = ''
  3229. for key in sorted(annotations.iterkeys()) :
  3230. # take all values, get their type and concatenate
  3231. fmt = ''
  3232. data = []
  3233. value = annotations[key]
  3234. if isinstance( value, (int,np.int32,np.int64) ) :
  3235. # elphy type 2
  3236. fmt = '<Bq'
  3237. data = [2, value]
  3238. elif type( value ) == str :
  3239. # elphy type 4
  3240. str_len = len(value)
  3241. fmt = '<BI'+str(str_len)+'s'
  3242. data = [4, str_len, value]
  3243. else :
  3244. print("ElphyIO.write_block() - unknown annotation type: %s" % type(value))
  3245. continue
  3246. # last, serialization
  3247. # BUF values
  3248. serialized_BUF_data += struct.pack( fmt, *data )
  3249. # ST values
  3250. # take each key and concatenate using 'crlf'
  3251. st_fmt += str(len(key))+'s2s'
  3252. st_data.extend( [ key, "\r\n" ] )
  3253. # ST keys
  3254. serialized_ST_data = struct.pack( st_fmt, *st_data )
  3255. # SpikeTrains
  3256. # serialized spike trains
  3257. serialized_spike_data += struct.pack( spiketrain_data_fmt, *spiketrains )
  3258. # ------------- Elphy Structures to be filled --------------
  3259. # 'Ep'
  3260. data_format = '<BiBB10sdd?BBddiddB10sB10sdI'
  3261. # setting values
  3262. uX = 'ms '
  3263. pc_time = datetime.now()
  3264. pc_time = pc_time.microsecond * 1000
  3265. data_values = [
  3266. nbchan, # nbchan : byte
  3267. nbpt, # nbpt : integer - nominal number of samples per channel
  3268. 0, # tpData : byte - not used
  3269. 10, # uX length
  3270. uX, # uX : string - time units
  3271. Dxu, # Dxu : double - sampling rate, scaling parameters on time axis
  3272. 0.0, # X0u : double - starting, scaling parameters on time axis
  3273. False, # continuous : boolean
  3274. 0, # TagMode : byte - 0: not a tag channel
  3275. 0, # TagShift : byte
  3276. Dxu, # DxuSpk : double
  3277. X0uSpk, # X0uSpk : double
  3278. NbVeV, # nbSpk : integer
  3279. 0.0, # DyuSpk : double
  3280. 0.0, # Y0uSpk : double
  3281. 10, # uX length
  3282. uX, # unitXSpk : string
  3283. 10, # uX length
  3284. ' ', # unitYSpk : string
  3285. CyberTime, # CyberTime : double
  3286. pc_time # PCtime : longword - time in milliseconds
  3287. ]
  3288. Ep_chr = self.get_serialized( data_format, data_values )
  3289. Ep_sub = self.get_serialized_subblock( 'Ep', Ep_chr )
  3290. # 'Adc'
  3291. # Then, one or more (nbchan) Analog/Digital Channel will be, having their fixed data format
  3292. data_format = "<B10sdd"
  3293. # when Ep.tpdata is an integer type, Dyu nad Y0u are parameters such that
  3294. # for an adc value j, the real value is y = Dyu*j + Y0u
  3295. Adc_chrl = ""
  3296. for dc in aa_units :
  3297. # create
  3298. Adc_chr = [] # init
  3299. Dyu,UnitY = '{}'.format(dc).split()
  3300. data_values = [
  3301. 10, # size
  3302. UnitY+' ', # uY string : vertical units
  3303. float(Dyu), # Dyu double : scaling parameter
  3304. 0.0 # Y0u double : scaling parameter
  3305. ]
  3306. Adc_chr = self.get_serialized( data_format, data_values )
  3307. Adc_chrl += Adc_chr
  3308. Adc_sub = self.get_serialized_subblock( 'Adc', Adc_chrl )
  3309. #print "Adc size:",len(Adc_sub)
  3310. # 'Ksamp'
  3311. # subblock containing an array of nbchan bytes
  3312. # data_format = '<h...' # nbchan times Bytes
  3313. # data_values = [ 1, 1, ... ] # nbchan times 1
  3314. data_format = "<" + ("h" * nbchan)
  3315. data_values = [ 1 for _ in range(nbchan) ]
  3316. Ksamp_chr = self.get_serialized( data_format, data_values )
  3317. Ksamp_sub = self.get_serialized_subblock( 'Ksamp', Ksamp_chr )
  3318. #print "Ksamp size: %s" % (len(Ksamp_sub))
  3319. # 'Ktype'
  3320. # subblock containing an array of nbchan bytes
  3321. # data_format = '<B...' # nbchan times Bytes
  3322. # data_values = [ 2, ... ] # nbchan times ctype
  3323. # Possible values are:
  3324. # 0: byte
  3325. # 1: short
  3326. # 2: smallint
  3327. # 3: word
  3328. # 4: longint
  3329. # 5: single
  3330. # 6: real48
  3331. # 7: double
  3332. # 8: extended DATA
  3333. # array of nbchan bytes specifying type of data forthcoming
  3334. ctype = 5 # single float
  3335. data_format = "<" + ("B" * nbchan)
  3336. data_values = [ ctype for n in range(nbchan) ]
  3337. Ktype_chr = self.get_serialized( data_format, data_values )
  3338. Ktype_sub = self.get_serialized_subblock( 'Ktype', Ktype_chr )
  3339. #print "Ktype size: %s" % (len(Ktype_sub))
  3340. # Episode data serialization:
  3341. # concatenate all its data strings under a block
  3342. Ep_data = Ep_sub + Adc_sub + Ksamp_sub + Ktype_sub
  3343. #print "\n---- Finishing:\nEp subs size: %s" % (len(Ep_data))
  3344. Ep_blk = self.get_serialized_block( 'B_Ep', Ep_data )
  3345. #print "B_Ep size: %s" % (len(Ep_blk))
  3346. # 'RDATA'
  3347. # It produces a two part (header+data) content coming from analog/digital inputs.
  3348. pctime = time()
  3349. data_format = "<h?dI"
  3350. data_values = [ 15, True, pctime, 0 ]
  3351. RDATA_chr = self.get_serialized( data_format, data_values, serialized_analog_data )
  3352. RDATA_blk = self.get_serialized_block( 'RDATA', RDATA_chr )
  3353. #print "RDATA size: %s" % (len(RDATA_blk))
  3354. # 'Rspk'
  3355. # like an REVT block + addons
  3356. # It starts with a RDATA header, after an integer with the number of events,
  3357. # then the events per channel and finally all the events one after the other
  3358. data_format = "<h?dII" + str(NbVeV) + "I"
  3359. data_values = [ 15, True, pctime, 0, NbVeV ]
  3360. data_values.extend(NbEv)
  3361. Rspk_chr = self.get_serialized( data_format, data_values, serialized_spike_data )
  3362. Rspk_blk = self.get_serialized_block( 'RSPK', Rspk_chr )
  3363. #print "RSPK size: %s" % (len(Rspk_blk))
  3364. # 'DBrecord'
  3365. # like a block + subblocks
  3366. # serializzation
  3367. ST_sub = self.get_serialized_subblock( 'ST', serialized_ST_data )
  3368. #print "ST size: %s" % (len(ST_sub))
  3369. BUF_sub = self.get_serialized_subblock( 'BUF', serialized_BUF_data )
  3370. #print "BUF size: %s" % (len(BUF_sub))
  3371. annotations_data = ST_sub + BUF_sub
  3372. #data_format = "<h?dI"
  3373. #data_values = [ 15, True, pctime, 0 ]
  3374. #DBrec_chr = self.get_serialized( data_format, data_values, annotations_data )
  3375. DBrec_blk = self.get_serialized_block( 'DBrecord', annotations_data )
  3376. #print "DBrecord size: %s" % (len(DBrec_blk))
  3377. # 'COM'
  3378. #print "write_block() - segment name:", seg.name
  3379. # name of the file - NEO Segment name
  3380. data_format = '<h'+str(len(seg.name))+'s'
  3381. data_values = [ len(seg.name), seg.name ]
  3382. SEG_COM_chr = self.get_serialized( data_format, data_values )
  3383. SEG_COM_blk = self.get_serialized_block( 'COM', SEG_COM_chr )
  3384. # Complete data serialization: concatenate all data strings
  3385. All += Ep_blk + RDATA_blk + Rspk_blk + DBrec_blk + SEG_COM_blk
  3386. # ElphyFile (open, write and close)
  3387. self.elphy_file.write( All )
  3388. def get_serialized( self, data_format, data_values, ext_data='' ):
  3389. data_chr = struct.pack( data_format, *data_values )
  3390. return data_chr + ext_data
  3391. def get_serialized_block( self, ident, data ):
  3392. """
  3393. Generic Block Header
  3394. This function (without needing a layout and the rest) creates a binary serialized version of
  3395. the block containing the format string and the actual data for the following
  3396. Elphy Block Header structure:
  3397. size: longint // 4-byte integer
  3398. ident: string[XXX]; // a Pascal variable-length string
  3399. data: array[1..YYY] of byte;
  3400. For example:
  3401. '<IB22s' followed by an array of bytes as specified
  3402. """
  3403. # endian 4byte ident
  3404. data_format = "<IB" + str(len(ident))+"s"
  3405. data_size = 4 + 1 + len(ident) + len(data) # all: <IBs...data...
  3406. data_values = [ data_size, len(ident), ident ]
  3407. data_chr = struct.pack( data_format, *data_values )
  3408. return data_chr + data
  3409. def get_serialized_subblock( self, ident, data ):
  3410. """
  3411. Generic Sub-Block Header
  3412. This function (without needing a layout and the rest) creates a binary serialized version of
  3413. the block containing the format string and the actual data for the following
  3414. Elphy Sub-Block Header structure:
  3415. id: string[XXX]; // a Pascal variable-length string
  3416. size1: word // 2-byte unsigned integer
  3417. data: array[1..YYY] of byte;
  3418. For example:
  3419. '<B22sH4522L' followed by an array of bytes as specified
  3420. """
  3421. data_size = len( data )
  3422. # endian size+string 2byte array of data_size bytes
  3423. data_format = "<B" + str(len(ident))+"s" + "h"
  3424. data_values = [ len(ident), ident, data_size ]
  3425. data_chr = struct.pack( data_format, *data_values )
  3426. return data_chr + data
  3427. def get_annotations_dict( self, annotations, prefix, items, name='', idx=0 ) :
  3428. """
  3429. Helper function to retrieve annotations in a dictionary to be serialized as Elphy DBrecord
  3430. """
  3431. for (key,value) in items :
  3432. #print "get_annotation_dict() - items[%s]" % (key)
  3433. if isinstance( value, (list,tuple,np.ndarray) ) :
  3434. for element in value :
  3435. annotations.update( dict( [(prefix+"-"+name+"-"+key+"-"+str(idx), element)] ) )
  3436. idx = idx+1
  3437. else :
  3438. annotations.update( dict( [(prefix+"-"+key+"-"+str(idx),value)] ) )
  3439. return (idx,annotations)
  3440. def read_segment( self, episode ):
  3441. """
  3442. Internal method used to return :class:`Segment` data to the main read method.
  3443. Parameters:
  3444. elphy_file : is the elphy object.
  3445. episode : number of elphy episode, roughly corresponding to a segment
  3446. """
  3447. #print "name:",self.elphy_file.layout.get_episode_name(episode)
  3448. episode_name = self.elphy_file.layout.get_episode_name(episode)
  3449. name = episode_name if len(episode_name)>0 else "episode %s" % str(episode + 1)
  3450. segment = Segment( name=name )
  3451. # create an analog signal for
  3452. # each channel in the episode
  3453. for channel in range(1, self.elphy_file.n_channels(episode)+1) :
  3454. signal = self.elphy_file.get_signal(episode, channel)
  3455. analog_signal = AnalogSignal(
  3456. signal.data['y'],
  3457. units = signal.y_unit,
  3458. t_start = signal.t_start * getattr(pq, signal.x_unit.strip()),
  3459. t_stop = signal.t_stop * getattr(pq, signal.x_unit.strip()),
  3460. #sampling_rate = signal.sampling_frequency * pq.kHz,
  3461. sampling_period = signal.sampling_period * getattr(pq, signal.x_unit.strip()),
  3462. channel_name="episode %s, channel %s" % ( int(episode+1), int(channel+1) )
  3463. )
  3464. analog_signal.segment = segment
  3465. segment.analogsignals.append(analog_signal)
  3466. # create a spiketrain for each
  3467. # spike channel in the episode
  3468. # in case of multi-electrode
  3469. # acquisition context
  3470. n_spikes = self.elphy_file.n_spiketrains(episode)
  3471. #print "read_segment() - n_spikes:",n_spikes
  3472. if n_spikes>0 :
  3473. for spk in range(1, n_spikes+1) :
  3474. spiketrain = self.read_spiketrain(episode, spk)
  3475. spiketrain.segment = segment
  3476. segment.spiketrains.append( spiketrain )
  3477. # segment
  3478. return segment
  3479. def read_channelindex( self, episode ):
  3480. """
  3481. Internal method used to return :class:`ChannelIndex` info.
  3482. Parameters:
  3483. elphy_file : is the elphy object.
  3484. episode : number of elphy episode, roughly corresponding to a segment
  3485. """
  3486. n_spikes = self.elphy_file.n_spikes
  3487. group = ChannelIndex(
  3488. name="episode %s, group of %s electrodes" % (episode, n_spikes)
  3489. )
  3490. for spk in range(0, n_spikes) :
  3491. channel = self.read_channelindex(episode, spk)
  3492. group.channel_indexes.append(channel)
  3493. return group
  3494. def read_recordingchannel( self, episode, chl ):
  3495. """
  3496. Internal method used to return a :class:`RecordingChannel` label.
  3497. Parameters:
  3498. elphy_file : is the elphy object.
  3499. episode : number of elphy episode, roughly corresponding to a segment.
  3500. chl : electrode number.
  3501. """
  3502. channel = RecordingChannel(
  3503. name="episode %s, electrodes %s" % (episode, chl)
  3504. )
  3505. return channel
  3506. def read_event( self, episode, evt ):
  3507. """
  3508. Internal method used to return a list of elphy :class:`EventArray` acquired from event channels.
  3509. Parameters:
  3510. elphy_file : is the elphy object.
  3511. episode : number of elphy episode, roughly corresponding to a segment.
  3512. evt : index of the event.
  3513. """
  3514. event = self.elphy_file.get_event(episode, evt)
  3515. neo_event = Event(
  3516. times=event.times * pq.s,
  3517. channel_name="episode %s, event channel %s" % (episode + 1, evt + 1)
  3518. )
  3519. return neo_event
  3520. def read_spiketrain( self, episode, spk ):
  3521. """
  3522. Internal method used to return an elphy object :class:`SpikeTrain`.
  3523. Parameters:
  3524. elphy_file : is the elphy object.
  3525. episode : number of elphy episode, roughly corresponding to a segment.
  3526. spk : index of the spike array.
  3527. """
  3528. block = self.elphy_file.layout.episode_block(episode)
  3529. spike = self.elphy_file.get_spiketrain(episode, spk)
  3530. spikes = spike.times * pq.s
  3531. #print "read_spiketrain() - spikes: %s" % (len(spikes))
  3532. #print "read_spiketrain() - spikes:",spikes
  3533. dct = {
  3534. 'times':spikes,
  3535. 't_start': block.ep_block.X0_wf if block.ep_block.X0_wf < spikes[0] else spikes[0], #check
  3536. 't_stop': block.ep_block.cyber_time if block.ep_block.cyber_time > spikes[-1] else spikes[-1],
  3537. 'units':'s',
  3538. # special keywords to identify the
  3539. # electrode providing the spiketrain
  3540. # event though it is redundant with
  3541. # waveforms
  3542. 'label':"episode %s, electrode %s" % (episode, spk),
  3543. 'electrode_id':spk
  3544. }
  3545. # new spiketrain
  3546. return SpikeTrain(**dct)