def _adjustPositionForward(self, pair, maxOffset =1.0e8): """_adjustPositionForward doc...""" delta = 0.001 segment = pair['segment'] offset = segment.offset + pair['distance'] while maxOffset <= (offset + delta) or NumericUtils.equivalent(maxOffset, offset + delta): delta *= 0.5 point = pair['line'].start.clone() segment.line.adjustPointAlongLine(point, delta, inPlace=True) dist = segment.line.start.distanceTo(point).raw if not NumericUtils.equivalent(pair['distance'] + delta, dist, machineEpsilonFactor=1000.0): self.stage.logger.write([ '[ERROR]: Forward adjust failure in CurveSeries.adjustPositionForward', 'TRACK: %s [%s]' % (pair['track'].fingerprint, pair['track'].uid), 'EXPECTED: %s' % (pair['distance'] + delta), 'ACTUAL: %s' % dist, 'DELTA: %s' % delta]) pair['line'].start.copyFrom(point) track = pair['track'] pair['distance'] = dist if not self.saveToAnalysisTracks: return at = track.getAnalysisPair(self.stage.analysisSession) at.curvePosition = segment.offset + dist at.segmentPosition = dist
def _resolveSpatialCoincidences(self, pair): """ Correct for cases where projected prints reside at the same spatial location on the curve series by adjusting one of the tracks projection position slightly. """ segment = pair['segment'] try: nextSegment = self.segments[self.segments.index(segment) + 1] nextOffset = nextSegment.offset except Exception: nextOffset = 1.0e8 # Adjust a pair print if it resides at the same position as its curve series track if NumericUtils.equivalent(pair['distance'], 0.0): self._adjustPositionForward(pair, nextOffset) try: # Retrieve the next pair track in the segment if one exists nextPair = segment.pairs[segment.pairs.index(pair) + 1] except Exception: return pDist = pair['distance'] npDist = nextPair['distance'] # Adjust pair tracks that reside at the same spatial position if npDist <= pDist or NumericUtils.equivalent(pDist, npDist): self._adjustPositionForward(nextPair, nextOffset)
def _drawMeasuredWidth(self, track, drawing): if NumericUtils.equivalent(track.widthMeasured, 0.0): return if track.uid in self.trackDeviations: data = self.trackDeviations[track.uid] if data['wSigma'] > 2.0: strokeWidth = 3.0 color = 'red' else: strokeWidth = 1.0 color = 'green' else: strokeWidth = 1.0 color = 'green' w = 100*track.widthMeasured rot = math.radians(track.rotation) x1 = w/2.0 x2 = -w/2.0 drawing.line( (track.x + x1*math.cos(rot), track.z - x1*math.sin(rot)), (track.x + x2*math.cos(rot), track.z - x2*math.sin(rot)), scene=True, stroke=color, stroke_width=strokeWidth)
def _drawMeasuredLength(self, track, drawing): if NumericUtils.equivalent(track.lengthMeasured, 0.0): return if track.uid in self.trackDeviations: data = self.trackDeviations[track.uid] if data['lSigma'] > 2.0: strokeWidth = 3.0 color = 'red' else: strokeWidth = 1.0 color = 'green' else: strokeWidth = 1.0 color = 'green' l = 100*track.lengthMeasured rot = math.radians(track.rotation) z1 = track.lengthRatio*l z2 = z1 - l drawing.line( (track.x + z1*math.sin(rot), track.z + z1*math.cos(rot)), (track.x + z2*math.sin(rot), track.z + z2*math.cos(rot)), scene=True, stroke=color, stroke_width=strokeWidth)
def _plot(self): """_plot doc...""" x = [] y = [] yUnc = [] for value in self.data: entry = self._dataItemToValue(value) y.append(entry['y']) x.append(entry.get('x', len(x))) yUnc.append(entry.get('yUnc', 0.0)) if NumericUtils.equivalent(max(yUnc), 0.0): yUnc = None pl = self.pl pl.bar(x, y, yerr=yUnc, facecolor=self.color, edgecolor=self.strokeColor, log=self.isLog) pl.title(self.title) pl.xlabel(self.xLabel) pl.ylabel(self.yLabel) if self.xLimits: pl.xlim(*self.xLimits) if self.yLimits: pl.ylim(*self.yLimits) pl.grid(True)
def _extrapolateByLength(self, lengthAdjust, pre =False): """_extrapolateByLength doc...""" length = self.length targetLengthSqr = (length.raw + lengthAdjust)**2 s = self.start e = self.end deltaX = e.x - s.x deltaY = e.y - s.y delta = lengthAdjust if NumericUtils.equivalent(deltaX, 0.0): # Vertical lines should invert delta if start is above the end delta *= -1.0 if deltaY < 0.0 else 1.0 elif deltaX < 0.0: # Other lines should invert delta if start is right of the end delta *= -1.0 if pre: delta *= -1.0 startY = s.y prevX = s.x point = self.end else: startY = e.y prevX = e.x point = self.start if NumericUtils.equivalent(deltaX, 0.0): return s.x, startY + delta i = 0 while i < 100000: x = prevX + delta y = s.y + deltaY*(x - s.x)/deltaX testLengthSqr = math.pow(x - point.x, 2) + math.pow(y - point.y, 2) if NumericUtils.equivalent(testLengthSqr/targetLengthSqr, 1.0, 0.000001): return x, y elif testLengthSqr > targetLengthSqr: delta *= 0.5 else: prevX = x i += 1 raise ValueError('Unable to extrapolate line segment to specified length')
def project(self, track, data =None): """ Tests the specified segment and modifies the data dictionary with the results of the test if it was successful. """ data = self._initializeData(data, track) position = track.positionValue debugItem = {'TRACK':self.track.fingerprint if self.track else 'NONE'} debugData = {} data['debug'].append({'print':debugItem, 'data':debugData}) # Make sure the track resides in a generally forward direction relative to # the direction of the segment. The prevents tracks from matching from behind. angle = self.line.angleBetweenPoint(position) if abs(angle.degrees) > 100.0: debugItem['CAUSE'] = 'Segment position angle [%s]' % angle.prettyPrint return # Calculate the closest point on the line segment. If the point and line are not # properly coincident, the testPoint will be None and the attempt should be aborted. testPoint = self.line.closestPointOnLine(position, contained=True) if not testPoint: debugItem['CAUSE'] = 'Not aligned to segment' return testLine = LineSegment2D(testPoint, position.clone()) # Make sure the test line intersects the segment line at 90 degrees, or the # value is invalid. angle = testLine.angleBetweenPoint(self.line.end) if not NumericUtils.equivalent(angle.degrees, 90.0, 2.0): debugItem['CAUSE'] = 'Projection angle [%s]' % angle.prettyPrint debugData['testLine'] = testLine debugData['testPoint'] = testPoint return # Skip if the test line length is greater than the existing test line length = data.get('projectionLength', 1.0e10) if testLine.length.raw > length: debugItem['CAUSE'] = 'Greater length [%s > %s]' % ( NumericUtils.roundToSigFigs(length, 5), NumericUtils.roundToSigFigs(testLine.length.raw, 5) ) debugData['testLine'] = testLine debugData['testPoint'] = testPoint return # Populate the projection values if the projection was successful p = testPoint.clone() # p.xUnc = position.xUnc # p.yUnc = position.yUnc data['segment'] = self data['projectionLength'] = position.distanceTo(p).raw data['line'] = LineSegment2D(p, position) data['distance'] = self.line.start.distanceTo(p).raw debugData['distance'] = data['distance'] return data
def angleBetween(self, position): """angleBetween doc...""" myLength = self.length posLength = position.length denom = myLength.raw*posLength.raw denomUnc = math.sqrt( myLength.rawUncertainty*myLength.rawUncertainty + posLength.rawUncertainty*posLength.rawUncertainty) if denom == 0.0: return Angle(radians=0.0, uncertainty=0.5*math.pi) nom = self.x*position.x + self.y*position.y nomUnc = (abs(position.x)*self.xUnc + abs(self.x)*position.xUnc + abs(position.y)*self.yUnc + abs(self.y)*position.yUnc)/denom b = nom/denom bUnc = abs(1.0/denom)*nomUnc + \ abs(nom/math.pow(denom, 2))*denomUnc if NumericUtils.equivalent(b, 1.0): return Angle() try: if NumericUtils.equivalent(b, -1.0): a = math.pi else: a = math.acos(b) except Exception: print('[ERROR]: Unable to calculate angle between', b) return Angle() if NumericUtils.equivalent(a, math.pi): return Angle(radians=a, uncertainty=180.0) try: aUnc = abs(1.0/math.sqrt(1.0 - b*b))*bUnc except Exception: print('[ERROR]: Unable to calculate angle between uncertainty', b, a) return Angle() return Angle(radians=a, uncertainty=aUnc)
def angleBetween(self, position): """angleBetween doc...""" myLength = self.length posLength = position.length denom = myLength.raw * posLength.raw denomUnc = math.sqrt( myLength.rawUncertainty * myLength.rawUncertainty + posLength.rawUncertainty * posLength.rawUncertainty) if denom == 0.0: return Angle(radians=0.0, uncertainty=0.5 * math.pi) nom = self.x * position.x + self.y * position.y nomUnc = (abs(position.x) * self.xUnc + abs(self.x) * position.xUnc + abs(position.y) * self.yUnc + abs(self.y) * position.yUnc) / denom b = nom / denom bUnc = abs(1.0/denom)*nomUnc + \ abs(nom/math.pow(denom, 2))*denomUnc if NumericUtils.equivalent(b, 1.0): return Angle() try: if NumericUtils.equivalent(b, -1.0): a = math.pi else: a = math.acos(b) except Exception: print('[ERROR]: Unable to calculate angle between', b) return Angle() if NumericUtils.equivalent(a, math.pi): return Angle(radians=a, uncertainty=180.0) try: aUnc = abs(1.0 / math.sqrt(1.0 - b * b)) * bUnc except Exception: print('[ERROR]: Unable to calculate angle between uncertainty', b, a) return Angle() return Angle(radians=a, uncertainty=aUnc)
def _analyzeTrack(self, track, series, trackway, sitemap): trackId = '%s (%s)' % (track.fingerprint, track.uid) if NumericUtils.equivalent(track.width, 0.0): self.logger.write('[ERROR]: Zero track width %s' % trackId) if NumericUtils.equivalent(track.widthUncertainty, 0.0): self.logger.write('[ERROR]: Zero track width uncertainty %s' % trackId) if NumericUtils.equivalent(track.length, 0.0): self.logger.write('[ERROR]: Zero track length %s' % trackId) if NumericUtils.equivalent(track.lengthUncertainty, 0.0): self.logger.write('[ERROR]: Zero track length uncertainty %s' % trackId) self._tracks.append(track) x = track.xValue self._uncs.append(x.uncertainty) z = track.zValue self._uncs.append(z.uncertainty)
def getNormalityColor(normality, goodChannel, badChannel): if NumericUtils.equivalent(normality, -1.0, 0.01): return ColorValue({'r':100, 'g':100, 'b':100}) test = min(1.0, max(0.0, normality)) limit = NORMAL_THRESHOLD slope = 1.0/(1.0 - limit) intercept = -slope*limit test = min(1.0, max(0.0, slope*test + intercept)) color = dict(r=0.0, b=0.0, g=0.0) color[badChannel] = max(0, 255.0*min(1.0, 1.0 - 1.0*test)) color[goodChannel] = max(0, min(255, 255.0*test)) return ColorValue(color)
def _drawWidth(self, track, drawing, color ='orange', strokeWidth =0.5): if NumericUtils.equivalent(track.widthMeasured, 0.0): return w = 100*track.width rot = math.radians(track.rotation) x1 = w/2.0 x2 = -w/2.0 drawing.line( (track.x + x1*math.cos(rot), track.z - x1*math.sin(rot)), (track.x + x2*math.cos(rot), track.z - x2*math.sin(rot)), scene=True, stroke=color, stroke_width=strokeWidth)
def _drawLength(self, track, drawing, color ='orange', strokeWidth =0.5): if NumericUtils.equivalent(track.lengthMeasured, 0.0): return l = 100*track.length rot = math.radians(track.rotation) z1 = track.lengthRatio*l z2 = z1 - l # draw the length line drawing.line( (track.x + z1*math.sin(rot), track.z + z1*math.cos(rot)), (track.x + z2*math.sin(rot), track.z + z2*math.cos(rot)), scene=True, stroke=color, stroke_width=strokeWidth)
def _getNextTrack(self, track, trackway): """ Iterates through all the tracks in the trackway and finds the track closest to the specified track. If the track argument is None the first track in the trackway will be returned. """ bundle = self.owner.getSeriesBundle(trackway) trackPosition = -1.0e8 targetPosition = 1.0e8 nextTrack = None anaTrack = None if track: anaTrack = track.getAnalysisPair(self.analysisSession) trackPosition = anaTrack.curvePosition for value in bundle.asList(): for t in value.tracks: if track and t == track: continue at = t.getAnalysisPair(self.analysisSession) if at.curvePosition < trackPosition: continue sigFigsRound = NumericUtils.roundToSigFigs if NumericUtils.equivalent(at.curvePosition, targetPosition): log = [ '[ERROR]: Multiple tracks at the same curve location', 'TARGET: %s' % sigFigsRound(targetPosition, 5), 'TEST: %s [%s]' % (t.fingerprint, t.uid), 'LOCATION[TEST]: %s (%s)' % ( sigFigsRound(at.curvePosition, 5), sigFigsRound(at.segmentPosition, 5) ) ] if nextTrack: nat = nextTrack.getAnalysisPair(self.analysisSession) log.append('COMPARE: %s [%s]' % ( nextTrack.fingerprint, nextTrack.uid )) log.append('LOCATION[COMP]: %s (%s)' % ( sigFigsRound(nat.curvePosition, 5), sigFigsRound(nat.segmentPosition, 5) )) if track: log.append('TRACK: %s [%s]' % ( track.fingerprint, track.uid )) log.append('LOCATION[TRACK]: %s (%s)' % ( sigFigsRound(anaTrack.curvePosition, 5), sigFigsRound(anaTrack.segmentPosition, 5) )) self.logger.write(log) raise ValueError( 'Found multiple tracks at the same curve location' ) if at.curvePosition < targetPosition: nextTrack = t targetPosition = at.curvePosition else: break return nextTrack
def _addQuartileEntry(self, label, trackway, data): if not data or len(data) < 4: return if label not in self._quartileStats: csv = CsvWriter() csv.path = self.getPath( '%s-Quartiles.csv' % label.replace(' ', '-'), isFile=True) csv.autoIndexFieldName = 'Index' csv.addFields( ('name', 'Name'), ('normality', 'Normality'), ('unweightedNormality', 'Unweighted Normality'), ('unweightedLowerBound', 'Unweighted Lower Bound'), ('unweightedLowerQuart', 'Unweighted Lower Quartile'), ('unweightedMedian', 'Unweighted Median'), ('unweightedUpperQuart', 'Unweighted Upper Quartile'), ('unweightedUpperBound', 'Unweighted Upper Bound'), ('lowerBound', 'Lower Bound'), ('lowerQuart', 'Lower Quartile'), ('median', 'Median'), ('upperQuart', 'Upper Quartile'), ('upperBound', 'Upper Bound'), ('diffLowerBound', 'Diff Lower Bound'), ('diffLowerQuart', 'Diff Lower Quartile'), ('diffMedian', 'Diff Median'), ('diffUpperQuart', 'Diff Upper Quartile'), ('diffUpperBound', 'Diff Upper Bound') ) self._quartileStats[label] = csv csv = self._quartileStats[label] dd = mstats.density.Distribution(data) unweighted = mstats.density.boundaries.unweighted_two(dd) weighted = mstats.density.boundaries.weighted_two(dd) #----------------------------------------------------------------------- # PLOT DENSITY # Create a density plot for each value p = MultiScatterPlot( title='%s %s Density Distribution' % (trackway.name, label), xLabel=label, yLabel='Probability (AU)') x_values = mstats.density.ops.adaptive_range(dd, 10.0) y_values = dd.probabilities_at(x_values=x_values) p.addPlotSeries( line=True, markers=False, label='Weighted', color='blue', data=zip(x_values, y_values) ) temp = mstats.density.create_distribution( dd.naked_measurement_values(raw=True) ) x_values = mstats.density.ops.adaptive_range(dd, 10.0) y_values = dd.probabilities_at(x_values=x_values) p.addPlotSeries( line=True, markers=False, label='Unweighted', color='red', data=zip(x_values, y_values) ) if label not in self._densityPlots: self._densityPlots[label] = [] self._densityPlots[label].append( p.save(self.getTempFilePath(extension='pdf'))) #----------------------------------------------------------------------- # NORMALITY # Calculate the normality of the weighted and unweighted # distributions as a test against how well they conform to # the Normal distribution calculated from the unweighted data. # # The unweighted Normality test uses a basic bandwidth detection # algorithm to create a uniform Gaussian kernel to populate the # DensityDistribution. It is effectively a density kernel # estimation, but is aggressive in selecting the bandwidth to # prevent over-smoothing multi-modal distributions. if len(data) < 8: normality = -1.0 unweightedNormality = -1.0 else: result = NumericUtils.getMeanAndDeviation(data) mean = result.raw std = result.rawUncertainty normality = mstats.density.ops.overlap( dd, mstats.density.create_distribution([mean], [std]) ) rawValues = [] for value in data: rawValues.append(value.value) ddRaw = mstats.density.create_distribution(rawValues) unweightedNormality = mstats.density.ops.overlap( ddRaw, mstats.density.create_distribution([mean], [std]) ) # Prevent divide by zero unweighted = [ 0.00001 if NumericUtils.equivalent(x, 0) else x for x in unweighted ] csv.addRow({ 'index':trackway.index, 'name':trackway.name, 'normality':normality, 'unweightedNormality':unweightedNormality, 'unweightedLowerBound':unweighted[0], 'unweightedLowerQuart':unweighted[1], 'unweightedMedian' :unweighted[2], 'unweightedUpperQuart':unweighted[3], 'unweightedUpperBound':unweighted[4], 'lowerBound':weighted[0], 'lowerQuart':weighted[1], 'median' :weighted[2], 'upperQuart':weighted[3], 'upperBound':weighted[4], 'diffLowerBound':abs(unweighted[0] - weighted[0])/unweighted[0], 'diffLowerQuart':abs(unweighted[1] - weighted[1])/unweighted[1], 'diffMedian' :abs(unweighted[2] - weighted[2])/unweighted[2], 'diffUpperQuart':abs(unweighted[3] - weighted[3])/unweighted[3], 'diffUpperBound':abs(unweighted[4] - weighted[4])/unweighted[4] })
def test_equivalent(self): """test_equivalent doc...""" self.assertTrue(NumericUtils.equivalent(1.0, 1.001, 0.01)) self.assertFalse(NumericUtils.equivalent(1.0, 1.011, 0.01))
def _analyzeTrack(self, track, series, trackway, sitemap): if NumericUtils.equivalent(track.x, 0.0) and NumericUtils.equivalent(track.z, 0.0): self._tracks.append(track) self._csv.addRow({'uid':track.uid, 'fingerprint':track.fingerprint})
def test_equivalent(self): """test_equivalent doc...""" self.assertTrue(NumericUtils.equivalent(1.0, 1.001, 0.01)) self.assertFalse(NumericUtils.equivalent(1.0, 1.011, 0.01))