1515import org .lcsim .detector .ITransform3D ;
1616import org .lcsim .detector .tracker .silicon .ChargeCarrier ;
1717import org .lcsim .detector .tracker .silicon .HpsSiSensor ;
18- import org .lcsim .detector .tracker .silicon .SiStrips ;
18+ import org .lcsim .detector .tracker .silicon .SiSensorElectrodes ;
19+ import org .lcsim .detector .tracker .silicon .SiStriplets ;
1920import org .lcsim .event .EventHeader ;
2021import org .lcsim .event .GenericObject ;
2122import org .lcsim .event .RawTrackerHit ;
@@ -155,10 +156,15 @@ public static Map<Integer, Hep3Vector> createStripPositionMap(HpsSiSensor sensor
155156 Map <Integer , Hep3Vector > positionMap = new HashMap <Integer , Hep3Vector >();
156157 for (ChargeCarrier carrier : ChargeCarrier .values ()) {
157158 if (sensor .hasElectrodesOnSide (carrier )) {
158- SiStrips strips = (SiStrips ) sensor .getReadoutElectrodes (carrier );
159+ // Declared as the interface rather than SiStrips: L0 sensors
160+ // (HpsThinSiSensor) carry SiStriplets, which extend SiPixels and
161+ // are not SiStrips. getCellPosition() is on the interface and
162+ // SiStriplets overrides it to return the striplet centre, so the
163+ // polymorphic call is correct for both.
164+ SiSensorElectrodes strips = sensor .getReadoutElectrodes (carrier );
159165 ITransform3D parentToLocal = sensor .getReadoutElectrodes (carrier ).getParentToLocal ();
160166 ITransform3D localToGlobal = sensor .getReadoutElectrodes (carrier ).getLocalToGlobal ();
161- for (int physicalChannel = 0 ; physicalChannel < 640 ; physicalChannel ++) {
167+ for (int physicalChannel = 0 ; physicalChannel < sensor . getNumberOfChannels () ; physicalChannel ++) {
162168 Hep3Vector localStripPosition = strips .getCellPosition (physicalChannel );
163169 Hep3Vector stripPosition = parentToLocal .transformed (localStripPosition );
164170 Hep3Vector globalStripPosition = localToGlobal .transformed (stripPosition );
@@ -168,7 +174,66 @@ public static Map<Integer, Hep3Vector> createStripPositionMap(HpsSiSensor sensor
168174 }
169175 return positionMap ;
170176 }
171-
177+
178+ /**
179+ * Get the number of readout columns of a sensor. Strip sensors (L1-L6, and
180+ * all layers of the pre-2019 geometry) have a single column. L0 striplet
181+ * sensors split their strips into two columns along the strip direction.
182+ *
183+ * @param sensor : HpsSiSensor
184+ * @return The number of columns, always >= 1
185+ */
186+ private static int getNumberOfColumns (HpsSiSensor sensor ) {
187+ SiSensorElectrodes electrodes = sensor .getReadoutElectrodes (ChargeCarrier .HOLE );
188+ // Axis 1 is the column axis for both SiStrips (where it is 1) and
189+ // SiStriplets (where it is 2), but only when the electrodes are 2D.
190+ return electrodes .getNAxes () > 1 ? electrodes .getNCells (1 ) : 1 ;
191+ }
192+
193+ /**
194+ * Get the readout column a physical channel belongs to.
195+ *
196+ * Note that SiSensorElectrodes.getColumnNumber(int) is NOT consistent across
197+ * implementations - SiStriplets returns the column, but SiStrips subclasses
198+ * return the strip number itself - so it can only be called on striplets.
199+ *
200+ * @param sensor : HpsSiSensor
201+ * @param physicalChannel : physical channel number
202+ * @return The column number, 0 for strip sensors
203+ */
204+ private static int getColumnNumber (HpsSiSensor sensor , int physicalChannel ) {
205+ SiSensorElectrodes electrodes = sensor .getReadoutElectrodes (ChargeCarrier .HOLE );
206+ if (electrodes instanceof SiStriplets ) {
207+ return ((SiStriplets ) electrodes ).getColumnNumber (physicalChannel );
208+ }
209+ return 0 ;
210+ }
211+
212+ /**
213+ * Get the plot name suffix identifying a column.
214+ *
215+ * Empty for single-column (strip) sensors, so that both the map keys and the
216+ * histogram names are unchanged from the pre-L0 behaviour.
217+ *
218+ * @param sensor : HpsSiSensor
219+ * @param column : column number
220+ * @return The suffix
221+ */
222+ private static String getColumnSuffix (HpsSiSensor sensor , int column ) {
223+ return getNumberOfColumns (sensor ) == 1 ? "" : " - Column " + column ;
224+ }
225+
226+ /**
227+ * Get the key used to store the per-column position plots of a sensor.
228+ *
229+ * @param sensor : HpsSiSensor
230+ * @param column : column number
231+ * @return The map key
232+ */
233+ private static String getPositionPlotKey (HpsSiSensor sensor , int column ) {
234+ return sensor .getName () + getColumnSuffix (sensor , column );
235+ }
236+
172237 //Grab the channel associated with the cluster based on position.
173238 private int getClusterChan (SiTrackerHitStrip1D h , HpsSiSensor sensor ){
174239 List <RawTrackerHit > rawhits = h .getRawHits ();
@@ -178,10 +243,12 @@ private int getClusterChan(SiTrackerHitStrip1D h, HpsSiSensor sensor){
178243 Hep3Vector pos_global = global .getPositionAsVector ();
179244 double clusterHitPos = pos_global .y ();
180245 RawTrackerHit hit = rawhits .get (0 );
181- int chan = hit .getIdentifierFieldValue ("strip" );
182- double diffmin = Math .abs (getStripPosition (sensor ,chan ).y () - clusterHitPos );
246+ double diffmin = Double .MAX_VALUE ;
183247 for (RawTrackerHit rawhit : rawhits ){
184248 int chanhit = rawhit .getIdentifierFieldValue ("strip" );
249+ // Unbonded channels past the last readout channel have no entry in
250+ // the strip position map.
251+ if (!sensor .isValidChannel (chanhit )) continue ;
185252 double diff = Math .abs (getStripPosition (sensor ,chanhit ).y () - clusterHitPos );
186253 if (diff < diffmin ){
187254 diffmin = diff ;
@@ -209,8 +276,10 @@ private void resetPlots() {
209276 clusterOccupancyPlots .get (sensor .getName ()).reset ();
210277
211278 if (enablePositionPlots ) {
212- positionPlots .get (sensor .getName ()).reset ();
213- clusterPositionPlots .get (sensor .getName ()).reset ();
279+ for (int column = 0 ; column < getNumberOfColumns (sensor ); column ++) {
280+ positionPlots .get (getPositionPlotKey (sensor , column )).reset ();
281+ clusterPositionPlots .get (getPositionPlotKey (sensor , column )).reset ();
282+ }
214283 //clusterPositionPlotCounts.get(sensor.getName()).reset();
215284 }
216285
@@ -219,8 +288,8 @@ private void resetPlots() {
219288 }
220289
221290 // Reset the hit counters.
222- occupancyMap .put (sensor .getName (), new int [640 ]);
223- clusterOccupancyMap .put (sensor .getName (), new int [640 ]);
291+ occupancyMap .put (sensor .getName (), new int [sensor . getNumberOfChannels () ]);
292+ clusterOccupancyMap .put (sensor .getName (), new int [sensor . getNumberOfChannels () ]);
224293 }
225294 }
226295
@@ -252,11 +321,18 @@ protected void detectorChanged(Detector detector) {
252321 occupancyPlots .put (sensor .getName (),histogramFactory .createHistogram1D (sensor .getName () + " - Occupancy" , nChan , 0 , nChan ));
253322 clusterOccupancyPlots .put (sensor .getName (),histogramFactory .createHistogram1D (sensor .getName () + " - Cluster Occupancy" , nChan , 0 , nChan ));
254323 if (enablePositionPlots ){
255- positionPlots .put (sensor .getName (),histogramFactory .createHistogram1D (sensor .getName () + " - Occupancy vs Position" , 1000 , -60 , 60 ));
256- clusterPositionPlots .put (sensor .getName (),histogramFactory .createHistogram1D (sensor .getName () + " - Cluster occupancy vs Position" , 1000 , -60 , 60 ));
324+ // Strip sensors have a single column and keep the original plot
325+ // names. L0 striplet sensors get one plot per column, so that the
326+ // channel to position mapping stays one to one within each plot.
327+ for (int column = 0 ; column < getNumberOfColumns (sensor ); column ++) {
328+ String key = getPositionPlotKey (sensor , column );
329+ String suffix = getColumnSuffix (sensor , column );
330+ positionPlots .put (key ,histogramFactory .createHistogram1D (sensor .getName () + " - Occupancy vs Position" + suffix , 1000 , -60 , 60 ));
331+ clusterPositionPlots .put (key ,histogramFactory .createHistogram1D (sensor .getName () + " - Cluster occupancy vs Position" + suffix , 1000 , -60 , 60 ));
332+ }
257333 }
258- occupancyMap .put (sensor .getName (), new int [640 ]);
259- clusterOccupancyMap .put (sensor .getName (), new int [640 ]);
334+ occupancyMap .put (sensor .getName (), new int [nChan ]);
335+ clusterOccupancyMap .put (sensor .getName (), new int [nChan ]);
260336 if (enableMaxSamplePlots )
261337 maxSamplePositionPlots .put (sensor .getName (),histogramFactory .createHistogram1D (sensor .getName () + " - Max Sample Number" , 6 , -0.5 , 5.5 ));
262338 }
@@ -342,8 +418,15 @@ public void process(EventHeader event) {
342418 }
343419
344420 if (maxSamplePosition == -1 || maxSamplePosition == maxSamplePositionFound ) {
345- occupancyMap .get (((HpsSiSensor ) rawHit .getDetectorElement ()).getName ())[rawHit
346- .getIdentifierFieldValue ("strip" )]++;
421+ HpsSiSensor sensor = (HpsSiSensor ) rawHit .getDetectorElement ();
422+ int strip = rawHit .getIdentifierFieldValue ("strip" );
423+ // Raw hits can carry the unbonded channel one past the last
424+ // readout channel (639 on a 639 channel strip sensor), which
425+ // StripMaker also drops. It has no strip position and no
426+ // histogram bin, so skip it rather than index past the counters.
427+ if (sensor .isValidChannel (strip )) {
428+ occupancyMap .get (sensor .getName ())[strip ]++;
429+ }
347430 }
348431
349432 if (enableMaxSamplePlots ) {
@@ -356,14 +439,18 @@ public void process(EventHeader event) {
356439 if (event .hasCollection (SiTrackerHitStrip1D .class , stripClusterCollectionName )) {
357440 List <SiTrackerHitStrip1D > stripHits1D = event .get (SiTrackerHitStrip1D .class , stripClusterCollectionName );
358441 for (SiTrackerHitStrip1D h : stripHits1D ) {
359- int chan = getClusterChan (h ,((HpsSiSensor ) h .getRawHits ().get (0 ).getDetectorElement ()));
360-
442+ HpsSiSensor sensor = (HpsSiSensor ) h .getRawHits ().get (0 ).getDetectorElement ();
443+ int chan = getClusterChan (h , sensor );
444+ if (!sensor .isValidChannel (chan )) {
445+ continue ;
446+ }
447+
361448 if (enableClusterTimeCuts ) {
362449 if (h .getTime () < clusterTimeCutMax && h .getTime () > clusterTimeCutMin ) {
363- clusterOccupancyMap .get ((( HpsSiSensor ) h . getRawHits (). get ( 0 ). getDetectorElement ()) .getName ())[chan ]++;
450+ clusterOccupancyMap .get (sensor .getName ())[chan ]++;
364451 }
365452 } else {
366- clusterOccupancyMap .get ((( HpsSiSensor ) h . getRawHits (). get ( 0 ). getDetectorElement ()) .getName ())[chan ]++;
453+ clusterOccupancyMap .get (sensor .getName ())[chan ]++;
367454 }
368455 }
369456 }
@@ -376,7 +463,9 @@ public void process(EventHeader event) {
376463 occupancyPlots .get (sensor .getName ()).reset ();
377464 clusterOccupancyPlots .get (sensor .getName ()).reset ();
378465 if (enablePositionPlots ) {
379- positionPlots .get (sensor .getName ()).reset ();
466+ for (int column = 0 ; column < getNumberOfColumns (sensor ); column ++) {
467+ positionPlots .get (getPositionPlotKey (sensor , column )).reset ();
468+ }
380469 }
381470 for (int channel = 0 ; channel < strips .length ; channel ++) {
382471 double stripOccupancy = (double ) strips [channel ] / (double ) eventCount ;
@@ -385,7 +474,7 @@ public void process(EventHeader event) {
385474
386475 if (enablePositionPlots ) {
387476 double stripPosition = this .getStripPosition (sensor , channel ).y ();
388- positionPlots .get (sensor . getName ( )).fill (stripPosition , stripOccupancy );
477+ positionPlots .get (getPositionPlotKey ( sensor , getColumnNumber ( sensor , channel ) )).fill (stripPosition , stripOccupancy );
389478 }
390479 }
391480 for (int channel = 0 ; channel < clusterStrips .length ; channel ++) {
@@ -395,7 +484,7 @@ public void process(EventHeader event) {
395484
396485 if (enablePositionPlots ) {
397486 double clusterPosition = this .getStripPosition (sensor , channel ).y ();
398- clusterPositionPlots .get (sensor . getName ( )).fill (clusterPosition , clusterOccupancy );
487+ clusterPositionPlots .get (getPositionPlotKey ( sensor , getColumnNumber ( sensor , channel ) )).fill (clusterPosition , clusterOccupancy );
399488 }
400489 }
401490 }
@@ -409,19 +498,30 @@ public void endOfData() {
409498 System .out .println ("%======================== Active Edge Sensor Occupancies =======================%" );
410499 System .out .println ("%===============================================================================%" );
411500 System .out .println ("% Total Events: " + eventCount );
412- // Calculate the occupancies at the sensor edge
413- int [] topActiveEdgeStripOccupancy = new int [6 ];
414- int [] bottomActiveEdgeStripOccupancy = new int [6 ];
501+ // Calculate the occupancies at the sensor edge. The number of layers is
502+ // taken from the geometry rather than assumed to be 6, since the 2019+
503+ // geometry adds L0 for a total of 7.
504+ int nLayers = 0 ;
505+ for (HpsSiSensor sensor : sensors ) {
506+ nLayers = Math .max (nLayers , getLayerNumber (sensor ));
507+ }
508+ int [] topActiveEdgeStripOccupancy = new int [nLayers ];
509+ int [] bottomActiveEdgeStripOccupancy = new int [nLayers ];
415510 for (HpsSiSensor sensor : sensors ) {
511+ // The active edge is the last channel on the positron side and the
512+ // second channel on the electron side. Both are sensor-size
513+ // dependent: 638 (of 639) and 1 for strip sensors, 511 (of 512) and 1
514+ // for L0 striplet sensors.
515+ int edgeChannel = sensor .getNumberOfChannels () - 1 ;
416516 if (sensor .isTopLayer () && sensor .isAxial ()) {
417517 if (sensor .getSide ().equals (HpsSiSensor .ELECTRON_SIDE )) {
418518 System .out .println ("% Top Layer " + getLayerNumber (sensor ) + " Hit Counts: "
419519 + occupancyMap .get (sensor .getName ())[1 ]);
420520 topActiveEdgeStripOccupancy [getLayerNumber (sensor ) - 1 ] += occupancyMap .get (sensor .getName ())[1 ];
421521 } else {
422522 System .out .println ("% Top Layer " + getLayerNumber (sensor ) + " Hit Counts: "
423- + occupancyMap .get (sensor .getName ())[638 ]);
424- topActiveEdgeStripOccupancy [getLayerNumber (sensor ) - 1 ] += occupancyMap .get (sensor .getName ())[638 ];
523+ + occupancyMap .get (sensor .getName ())[edgeChannel ]);
524+ topActiveEdgeStripOccupancy [getLayerNumber (sensor ) - 1 ] += occupancyMap .get (sensor .getName ())[edgeChannel ];
425525 }
426526 } else if (sensor .isBottomLayer () && sensor .isAxial ()) {
427527 if (sensor .getSide ().equals (HpsSiSensor .ELECTRON_SIDE )) {
@@ -430,13 +530,13 @@ public void endOfData() {
430530 bottomActiveEdgeStripOccupancy [getLayerNumber (sensor ) - 1 ] += occupancyMap .get (sensor .getName ())[1 ];
431531 } else {
432532 System .out .println ("% Bottom Layer " + getLayerNumber (sensor ) + " Hit Counts: "
433- + occupancyMap .get (sensor .getName ())[638 ]);
434- bottomActiveEdgeStripOccupancy [getLayerNumber (sensor ) - 1 ] += occupancyMap .get (sensor .getName ())[638 ];
533+ + occupancyMap .get (sensor .getName ())[edgeChannel ]);
534+ bottomActiveEdgeStripOccupancy [getLayerNumber (sensor ) - 1 ] += occupancyMap .get (sensor .getName ())[edgeChannel ];
435535 }
436536 }
437537 }
438538
439- for (int layerN = 0 ; layerN < 6 ; layerN ++) {
539+ for (int layerN = 0 ; layerN < nLayers ; layerN ++) {
440540 double topStripOccupancy = (double ) topActiveEdgeStripOccupancy [layerN ] / (double ) eventCount ;
441541 topStripOccupancy /= this .timeWindowWeight ;
442542 System .out .println ("% Top Layer " + (layerN + 1 ) + ": Occupancy in " + (24 / this .timeWindowWeight )
0 commit comments