elphyio.py 152 KB

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