def tapas_ranking_observables(config): tapas_popular_first_page = PlainUrlPage( 'https://tapas.io/comics?browse=POPULAR') tapas_trending_first_page = PlainUrlPage( 'https://tapas.io/comics?browse=TRENDING') tapas_staff_picks_first_page = PlainUrlPage( 'https://tapas.io/comics?browse=TAPASTIC') tapas_popular = Measurement('tapas.popular-comics', tapas_popular_first_page, featured_comics_in_order) tapas_trending = Measurement('tapas.trending-comics', tapas_trending_first_page, featured_comics_in_order) tapas_staff_picks = Measurement('tapas.staff-picks-comics', tapas_staff_picks_first_page, featured_comics_in_order) measurements = [tapas_popular, tapas_trending, tapas_staff_picks] return [ observable.rank_of_comic_in(config.comic_name, config.creator_name, m) for m in measurements ]
def webtoons_logged_in_dashboard_observables(config): if not config.webtoons_username: return [] webtoons_logged_in_dashboard = WebtoonsLoggedInPage( 'http://www.webtoons.com/challenge/titleStat?titleNo=81223', username=config.webtoons_username, password=config.webtoons_password) subs_selector = '//*[@id="content"]/div[2]/div[2]/div/div[2]/ul[1]/li[3]/span/text()' webtoons_subs = Measurement( 'webtoons.subs', webtoons_logged_in_dashboard, compose_parsers(integer_with_commas, body_of_tag(selector=subs_selector))) monthly_pv_selector = \ '//*[@id="content"]/div[2]/div[2]/div/div[2]/ul[2]/li[3]/span/text()' webtoons_monthly_pvs = Measurement( 'webtoons.monthly_pvs', webtoons_logged_in_dashboard, compose_parsers(integer_with_commas, body_of_tag(selector=monthly_pv_selector))) likes_selector = '//*[@id="content"]/div[2]/ul/li/div/p[2]/em/text()' webtoons_likes = Measurement( 'webtoons.likes', webtoons_logged_in_dashboard, compose_parsers(integer_with_commas, body_of_tag(selector=likes_selector))) measurements = [webtoons_subs, webtoons_likes, webtoons_monthly_pvs] return [observable.identity(m) for m in measurements]
def simulate(params=params, get_noise_record=lambda: None, plots=False): global sim, measurements for i in range(n_runs): sim = Simulation(params, get_noise_record()) measurement = Measurement(params) measurements.append(measurement) if plots: tracker = sim.encoder.get_tracker().clone() prev_distr = tracker.distr prev_lm_encoder = tracker.lm_encoder prev_lm_decoder = tracker.lm_decoder try: for t in sim.simulate(T): measurement.record(sim) if plots: if t == 1: if hasattr(prev_distr, 'is_hikmet'): plot_lloyd_max_hikmet(prev_distr, prev_lm_encoder.boundaries, prev_lm_decoder.levels, x_hit=sim.plant.x) else: plot_lloyd_max(prev_distr, prev_lm_encoder, prev_lm_decoder, x_hit=sim.plant.x) else: if hasattr(prev_distr, 'is_hikmet'): plot_lloyd_max_tracker_hikmet( prev_distr, prev_lm_encoder.boundaries, prev_lm_decoder.levels, tracker.d1, tracker.fw, x_hit=sim.plant.x) else: plot_lloyd_max_tracker(prev_distr, prev_lm_encoder, prev_lm_decoder, tracker, x_hit=sim.plant.x) tracker = sim.encoder.get_tracker().clone() prev_distr = tracker.distr prev_lm_encoder = tracker.lm_encoder prev_lm_decoder = tracker.lm_decoder print("Run {:d}, t = {:d} done".format(i, t)) except KeyboardInterrupt: print("Keyboard interrupt!") print(" Average power over channel: {:.4f}".format( sim.channel.average_power())) globals().update(params.all()) # Bring parameters into scope
def tapas_numerical_observables(config): tapas_comic_page = PlainUrlPage('http://tapas.io/series/{}'.format( config.comic_name)) tapas_creator_page = PlainUrlPage('https://tapas.io/{}/subscribers'.format( config.creator_name)) tapas_views = Measurement('tapas.views', tapas_comic_page, parse_tapas_views) tapas_subs = Measurement('tapas.subs', tapas_creator_page, parse_tapas_subs(config.creator_name)) measurements = [tapas_subs, tapas_views] return [observable.identity(m) for m in measurements]
def plot(average=True): figure = plt.figure() for measurement in measurements if not average \ else [Measurement.average(measurements)]: plt.figure(figure.number) measurement.plot_setup() measurement.plot_LQG() measurement.plot_bounds()
def webtoons_public_number_observables(config): webtoons_public_comic_page = PlainUrlPage( 'http://www.webtoons.com/en/challenge/camellia/list?title_no=81223'. format(config.creator_name)) views_selector = '//*[@id="_asideDetail"]/ul/li[2]/em/text()' webtoons_views = Measurement( 'webtoons.views', webtoons_public_comic_page, compose_parsers(number_in_thousands_with_suffix_K, body_of_tag(selector=views_selector))) rating_selector = '//*[@id="_starScoreAverage"]/text()' webtoons_rating = Measurement( 'webtoons.rating', webtoons_public_comic_page, compose_parsers(float_between_0_and_10, body_of_tag(selector=rating_selector))) measurements = [webtoons_rating, webtoons_views] return [observable.identity(m) for m in measurements]
def load_measurements(SNR_dB, alpha=1.2, quantizer_bits=1, code_blocklength=2): results = [] for i in count(1): filename = generate_filename(SNR_dB, alpha, i, quantizer_bits, code_blocklength) if os.path.isfile(filename): results.append(Measurement.load(filename)) else: break return results
def plot_compare_3(): import matplotlib matplotlib.rcParams.update({'font.size': 20, 'lines.linewidth': 3}) jscc = Measurement.load( 'data/comparison/alpha_1.2_SNR_4.5dB_KC_2--1-joint.p') sep = Measurement.load( 'data/comparison/alpha_1.2_SNR_4.5dB_KC_2--1-separate.p') jscc.plot_setup(label="t") sep.plot_LQG("2-PAM", ':') sep.plot_correctly_decoded(y=-15) jscc.plot_LQG("Spiral", '-') jscc.plot_bounds(upper_label="Spiral: analytic", lower_label="OPTA", upper_args=['-.'], lower_args=['--']) plt.legend(loc=(.4, .1))
def plot_compare(): jscc = Measurement.load('data/joint/alpha_1.001_SNR_2_KC_32-runs.p') separate1 = Measurement.load('data/separate/alpha_1.001_SNR_2_KC_2--1.p') separate2 = Measurement.load('data/separate/alpha_1.001_SNR_2_KC_2--2.p') plt.figure() jscc.plot_setup() # Plot in the right order so that the legend reads top-down separate1.plot_LQG("Separation, single run") separate2.plot_LQG("Separation, single run") jscc.plot_LQG("Spiral JSCC, 32-run average") jscc.plot_bounds(upper_label="Theoretical prediction (spiral JSCC)") plt.legend() plt.text(25, 5, jscc.params.text_description(), bbox={ 'facecolor': 'white', 'edgecolor': 'gray' })
def setUp(self): # Executes before every testcase self.single_point = Measurement(name=self.series, fields={'X': -100, 'Y': 720.0, 'T': 30.0}, timestamp='2013-12-30 10:36:43.567') self.dummies = DummyPoints(self.series, npoints=1000, decimals=4, delta_seconds=600) self.dbclient = InfluxDBClient(host=self.host, port=self.port, http_timeout=600, http_retries=3) self.dbclient.create_database(self.dbname) self.startTime = time()
def plot_compare_2(): jscc_avg = Measurement.load('data/joint/alpha_1.5_SNR_2_KC_2_256-runs.p') jscc = Measurement.load('data/comparison/alpha_1.5_SNR_2_KC_2--1-joint.p') sep = Measurement.load( 'data/comparison/alpha_1.5_SNR_2_KC_2--1-separate.p') jscc.plot_setup() sep.plot_LQG("Tandem with (2-PAM)$^2$") sep.plot_correctly_decoded() jscc.plot_LQG("Spiral JSCC, same noise sequences") jscc_avg.plot_LQG("Spiral JSCC, 256-run average") jscc.plot_bounds(upper_label="Theoretical prediction (spiral JSCC)") plt.legend(loc=(.55, .48)) plt.text(40, 1.6, jscc.params.text_description(), bbox={ 'facecolor': 'white', 'edgecolor': 'gray' })
def KITTI_detection_file_to_TargetSet(filename, time_per_time_step): ''' Inputs: - filename: (string) the location of the object detections file in KITTI format - time_per_time_step: (float) how much time elapses between time steps (or frames) Outputs: - measurementSet: (TargetSet) containing the measurements from filename ''' prev_frame_idx = -99 measurementSet = TargetSet() with open(filename, "r") as fh: for line in fh: # KITTI tracking benchmark data format: # (frame,tracklet_id,objectType,truncation,occlusion,alpha,x1,y1,x2,y2,h,w,l,X,Y,Z,ry) line = line.strip() fields = line.split(" ") frame_idx = int(round(float(fields[0]))) # frame x1 = float(fields[6]) # left [px] y1 = float(fields[7]) # top [px] x2 = float(fields[8]) # right [px] y2 = float(fields[9]) # bottom [px] bb_center = np.array([(x2 + x1) / 2.0, (y2 + y1) / 2.0]) width = x2 - x1 height = y2 - y1 time_stamp = frame_idx * time_per_time_step if frame_idx != prev_frame_idx: measurementSet.measurements.append( Measurement(time=time_stamp)) prev_frame_idx = frame_idx measurementSet.measurements[-1].val.append(bb_center) measurementSet.measurements[-1].widths.append(width) measurementSet.measurements[-1].heights.append(height) fh.close() return measurementSet
def __init__(self, frames): Ratio.__init__(self, Measurement(frames, Frame()), Measurement(1, Second()))
user = '******' password = '******' dbclient = InfluxDBClient(host=host, port=port, http_timeout=2) dbclient.create_database(dbname) dbclient.drop_database(dbname) dbclient.create_database(dbname) # dummies = DummyPoints('Tilt5', npoints=100000, decimals=4, delta_seconds=600) # dummies.dump('dump.txt', compress=False) # dumps points into text file # dummies.dump('dump.gz', compress=True) # dumps points into compressed gzip file # single_point = Measurement(name='TestSeries1', fields={'X': -100, 'Y': 720.0, 'T': 30.0}, timestamp='2015-12-30 10:36:43.567') # single Measurement instance with Timer('Single Measurement instance'): dbclient.write(dbname=dbname, points=single_point.to_bytes(), precision='n') # # single point as bytes # with Timer('Single point as bytes'): # dbclient.write(dbname=dbname, points=b'Tilt5 X=-22.34,Y=653.8676,T=-4.1 1045513396921781872\n', precision='n') # # generator (chunked HTTP POST) # with Timer('Generator (chunked POST)'): # dbclient.write(dbname=dbname, points=dummies, precision='n') # # bytearray (or bytes, dumped in-memory) (non-chunked, not compressed)
class DBClientTest(unittest.TestCase): # host = '46.101.128.140' host = '10.6.74.60' port = 8086 dbname = 'unittestdb' user = '******' password = '******' series = 'Tilt' def setUp(self): # Executes before every testcase self.single_point = Measurement(name=self.series, fields={'X': -100, 'Y': 720.0, 'T': 30.0}, timestamp='2013-12-30 10:36:43.567') self.dummies = DummyPoints(self.series, npoints=1000, decimals=4, delta_seconds=600) self.dbclient = InfluxDBClient(host=self.host, port=self.port, http_timeout=600, http_retries=3) self.dbclient.create_database(self.dbname) self.startTime = time() def tearDown(self): # Executes after every testcase t = time() - self.startTime print('Test <%s> executed in: %.3f seconds' % (self.id(), t)) self.dbclient.drop_database(self.dbname) #@unittest.skip("Skipped") def test_InfluxDBClient_constructor(self): dbclient = InfluxDBClient(host=self.host, port=self.port, http_timeout=60, http_retries=3) print(dbclient) #@unittest.skip("Skipped") def test_create_database(self): self.dbclient.create_database(self.dbname) #@unittest.skip("Skipped") def test_write_single_Measurement(self): r = self.dbclient.write(dbname=self.dbname, points=self.single_point.to_bytes(), precision='n') self.assertEqual(r.status_code, 204) #@unittest.skip("Skipped") def test_query(self): self.dbclient.write(dbname=self.dbname, points=self.single_point.to_bytes(), precision='n') q = self.dbclient.query(dbname=self.dbname, query='SELECT * FROM %s' % self.series) print('Single point query: ', q) self.assertEqual(q['results'][0]['series'][0]['values'][0][1], 30) #@unittest.skip("Skipped") def test_write_in_memory_dumped_bytearray(self): # bytearray (or bytes, dumped in-memory) (non-chunked, not compressed) self.dbclient.write(dbname=self.dbname, points=self.dummies.dump(), precision='n') #@unittest.skip("Skipped") def test_write_container(self): measurement1 = Measurement(name='TestSeries1', fields={'X': -100, 'Y': 720.0, 'T': 30.0}, timestamp='2013-12-30 10:36:43.567') measurement2 = Measurement(name='TestSeries1', fields={'X': -200, 'Y': 700.0, 'T': 25.0}, timestamp='2013-12-30 10:37:43.567') measurement3 = Measurement(name='TestSeries1', fields={'X': -20, 'Y': 70.0, 'T': 15.0}, timestamp='2013-12-30 10:38:43.567') container = Container(measurement1, measurement2) container.append(measurement3) print(container) self.dbclient.write(dbname=self.dbname, points=container.dump(decimals=4), precision='n') #@unittest.skip("Skipped") def test_chunked_write(self): # generator (chunked HTTP POST) r = self.dbclient.write(dbname=self.dbname, points=self.dummies, precision='n') self.assertEqual(r.status_code, 204) #@unittest.skip("Skipped") def test_write_gzipped_from_memory(self): # bytearray (or bytes, dumped in-memory) (non-chunked, compressed) r = self.dbclient.write(dbname=self.dbname, points=self.dummies.dump(compress=True), precision='n', gzipped=True) self.assertEqual(r.status_code, 204) #@unittest.skip("Skipped") def test_bulk_write_into_single_series(self): dummies = DummyPoints(self.series, npoints=5000, decimals=4, delta_seconds=600, opt='single_series') #print(dummies.dump()) r = self.dbclient.write(dbname=self.dbname, points=dummies.dump(), precision='n') self.assertEqual(r.status_code, 204) @unittest.skip("Skipped") def test_write_100000_points_in_5000_chunks_single_process(self): for chunk in range(0, 20): print('Sending chunk ', chunk) dummies = DummyPoints(self.series, npoints=5000, decimals=4, delta_seconds=600, opt='one_point_per_series') pts = dummies.dump() print('Sent bytes: ', len(pts)) r = self.dbclient.write(dbname=self.dbname, points=dummies.dump(), precision='n') self.assertEqual(r.status_code, 204) def write_worker(self, npoints, nchunks): for chunk in range(0, nchunks): print('Sending chunk ', chunk) dummies = DummyPoints(self.series, npoints=npoints, decimals=4, delta_seconds=6000, opt='one_point_per_series') self.dbclient.write(dbname=self.dbname, points=dummies.dump(), precision='n') @unittest.skip("Skipped") def test_write_100000_points_in_10000_chunks_multiprocess(self): nworkers = 20 npoints = 1000 nchunks = 10 workers = [] for i in range(nworkers): p = multiprocessing.Process(target=self.write_worker, args=(), kwargs={'npoints': npoints, 'nchunks': nchunks}) workers.append(p) p.start() for p in workers: p.join()
def time(self): measurement = Measurement(1, Second()).convertTo(Nano()) measurement.magnitude = time_ns() # Update the magnitude. return measurement
def gen_data_1source(params): ''' Generate synthetic data for 1 measurement source according to the specified parameters. The parameters contain the implicit parameters N (the dimension of the measurement space) and M (the dimension of the state space). Inputs: - params: A dictionary specifying the data generation parameters with entries * key: 'num_time_steps' * value: int, generate this many time steps of data * key: 'time_per_time_step' * value: float, time between succesive time steps (e.g. in seconds) * key: 'lamda_c' * value: float, Poisson parameter (average #) for clutter counts * key: ''lamda_b'' * value: float, Poisson parameter (average #) for birth counts * key: 'p_emission' * value: float, the probability a target emits a measurement * key: 'process_noise' * value: numpy array (MxM), process noise for motion of targets * key: 'meas_noise_target_state' * value: numpy array (NxN), measurement noise for valid target measurements * key: 'avg_bb_birth' * value: numpy array (2x1)CHECK vs. TRANSPOSE, mean bounding box dimensions for ground truth objects (GET BIRTH INSTEAD?) * key: 'var_bb_birth' *value: numpy array (2x2), covariance matrix for valid object bounding box dimensions * key: 'avg_bb_clutter' * value: numpy array (2x1)CHECK vs. TRANSPOSE, mean bounding box dimensions for clutter objects * key: 'var_bb_clutter' *value: numpy array (2x2), covariance matrix for clutter object bounding box dimensions * key: 'BORDER_DEATH_PROBABILITIES': BORDER_DEATH_PROBABILITIES * value: list of floats, ith entry is probability of target death after being unassociatied with a measurement for i time steps when the target is near the image border * key: 'NOT_BORDER_DEATH_PROBABILITIES': NOT_BORDER_DEATH_PROBABILITIES * value: list of floats, ith entry is probability of target death after being unassociatied with a measurement for i time steps when the target is not near the image border * key: 'init_vel_cov' * value: numpy array, covariance for sampling initial target velocities from Gaussian with mean 0 Outputs: - measurementSet: type TargetSet, contains generated measurements in measurementSet.measurements - groundTruthSet: type TargetSet, contains ground truth bounding boxes in groundTruthSet.measurements ''' params['init_vel_cov'] = np.asarray(params['init_vel_cov']) measurementSet = TargetSet() #contains generated measurements groundTruthSet = TargetSet() #contains ground truth target locations next_t_id = 0 for time_step in range(params['num_time_steps']): cur_time = time_step * params['time_per_time_step'] target_offscreen = False #whether any targets move offscreen measurementSet.measurements.append(Measurement(time=cur_time)) groundTruthSet.measurements.append(Measurement(time=cur_time)) for target in measurementSet.living_targets: #move targets target.move(params['time_per_time_step'], params['process_noise'] ) #IMPLEMENT ME!!! and kill if target goes offscreen if target.offscreen: target_offscreen = True if target_offscreen: #kill offscreen targets (remove from living_targets list) measurementSet.living_targets = [ t for t in measurementSet.living_targets if not t.offscreen ] target_died = False for target in measurementSet.living_targets: #add all target locations to ground truth target_position = np.dot(H, target.x).reshape(-1) groundTruthSet.measurements[-1].val.append(target_position) groundTruthSet.measurements[-1].widths.append(target.width) groundTruthSet.measurements[-1].heights.append(target.height) groundTruthSet.measurements[-1].ids.append(target.id_) #sample wether each target produces a measurement if np.random.random() < params['p_emission']: #sample measurement with noise if this target emits a measurement target_measurement = target.sample_measurement( params['meas_noise_target_state'], cur_time) measurementSet.measurements[-1].val.append(target_measurement) measurementSet.measurements[-1].widths.append(target.width) measurementSet.measurements[-1].heights.append(target.height) else: #sample target death for targets that do not produce measurements death_prob = target.target_death_prob( cur_time, cur_time - params['time_per_time_step'], params) if np.random.random() < death_prob: #kill the target target.alive = False target_died = True if target_died: #remove dead targets from living_targets list measurementSet.living_targets = [ t for t in measurementSet.living_targets if t.alive ] #sample the number of births if time_step == 0: birth_count = params['init_target_count'] else: birth_count = np.random.poisson(params['lamda_b']) if birth_count + len( measurementSet.living_targets) > params['max_target_count']: birth_count = params['max_target_count'] - len( measurementSet.living_targets) #sample the number of clutter objects clutter_count = np.random.poisson(params['lamda_c']) #create birth and clutter measurements for b in range(birth_count): bb_size = np.random.multivariate_normal(params['avg_bb_birth'], params['var_bb_birth']) x_pos = np.random.uniform( (X_MAX + X_MIN) / 2 - (X_MAX - X_MIN) / 6, (X_MAX + X_MIN) / 2 + (X_MAX - X_MIN) / 6) y_pos = np.random.uniform( (Y_MAX + Y_MIN) / 2 - (Y_MAX - Y_MIN) / 6, (Y_MAX + Y_MIN) / 2 + (Y_MAX - Y_MIN) / 6) new_target = TargetState( cur_time, next_t_id, BoundingBox(x_pos, y_pos, abs(bb_size[0]), abs(bb_size[1]), cur_time)) next_t_id += 1 target_velocity = np.random.multivariate_normal( [0, 0], params['init_vel_cov']) if params[ 'init_vel_to_center']: #point velocity towards image center target_velocity[0] = abs(target_velocity[0]) * np.sign( (X_MAX + X_MIN) / 2.0 - x_pos) target_velocity[1] = abs(target_velocity[1]) * np.sign( (Y_MAX + Y_MIN) / 2.0 - y_pos) new_target.x[(1, 0)] = target_velocity[0] new_target.x[(3, 0)] = target_velocity[1] measurementSet.living_targets.append(new_target) #add to ground truth target_position = np.dot(H, new_target.x).reshape(-1) groundTruthSet.measurements[-1].val.append(target_position) groundTruthSet.measurements[-1].widths.append(new_target.width) groundTruthSet.measurements[-1].heights.append(new_target.height) groundTruthSet.measurements[-1].ids.append(new_target.id_) #sample measurement with noise target_measurement = new_target.sample_measurement( params['meas_noise_target_state'], cur_time) measurementSet.measurements[-1].val.append(target_measurement) measurementSet.measurements[-1].widths.append(new_target.width) measurementSet.measurements[-1].heights.append(new_target.height) for c in range(clutter_count): bb_size = np.random.multivariate_normal(params['avg_bb_clutter'], params['var_bb_clutter']) x_pos = np.random.uniform(X_MIN, X_MAX) y_pos = np.random.uniform(Y_MIN, Y_MAX) measurementSet.measurements[-1].val.append(np.array([x_pos, y_pos])) measurementSet.measurements[-1].widths.append(bb_size[0]) measurementSet.measurements[-1].heights.append(bb_size[1]) #randomize the order of measurements if len(measurementSet.measurements[-1].val ) > 1: #if we have multiple measurements on this time step combined_list = list( zip(measurementSet.measurements[-1].val, measurementSet.measurements[-1].widths, measurementSet.measurements[-1].heights)) random.shuffle(combined_list) (measurementSet.measurements[-1].val, measurementSet.measurements[-1].widths, measurementSet.measurements[-1].heights) = zip(*combined_list) return (measurementSet, groundTruthSet)
def query(self): measurement = Measurement() modbus_device = self.rs485 measurement.volts = self.try_read(modbus_device, 0, 4, 2) measurement.current = self.try_read(modbus_device, 6, 4, 2) measurement.active_power = self.try_read(modbus_device, 12, 4, 2) measurement.apparent_power = self.try_read(modbus_device, 18, 4, 2) measurement.reactive_power = self.try_read(modbus_device, 24, 4, 2) measurement.power_factor = self.try_read(modbus_device, 30, 4, 2) measurement.phase_angle = self.try_read(modbus_device, 36, 4, 2) measurement.frequency = self.try_read(modbus_device, 70, 4, 2) measurement.import_active_energy = self.try_read( modbus_device, 72, 4, 2) measurement.export_active_energy = self.try_read( modbus_device, 74, 4, 2) measurement.import_reactive_energy = self.try_read( modbus_device, 76, 4, 2) measurement.export_reactive_energy = self.try_read( modbus_device, 78, 4, 2) measurement.total_active_energy = self.try_read( modbus_device, 342, 4, 2) measurement.total_reactive_energy = self.try_read( modbus_device, 344, 4, 2) return measurement
def time(self): measurement = Measurement(1, Second()).convertTo(Milli()) measurement.magnitude = get_ticks() # Update the magnitude. return measurement
def time(self): return Measurement(time(), Second())
def normalize(self): return Ratio(Measurement(self.numerator.magnitude / self.denominator.magnitude, self.numerator.unit), Measurement(1, self.denominator.unit))
def live(self): now = self.time() elapsedTime = Measurement(now.magnitude - self.lastTime.magnitude, now.unit) self.lastTime = now self.onElapsedTime(elapsedTime)
from measurements import Measurement from units.time import Second from units.prefixes.large import Kilo from units.prefixes.small import Milli seconds = Measurement(1.2, Second()) print(f"seconds: {seconds}") milliseconds = seconds.convertTo(Milli()) print(f"{seconds} = {milliseconds}") seconds = milliseconds.inBaseUnits() print(f"{milliseconds} = {seconds}") kiloseconds = seconds.convertTo(Kilo()) print(f"{seconds} = {kiloseconds}") seconds = kiloseconds.inBaseUnits() print(f"{kiloseconds} = {seconds}") milliseconds = kiloseconds.convertTo(Milli()) print(f"{kiloseconds} = {milliseconds}") kiloseconds = milliseconds.convertTo(Kilo()) print(f"{milliseconds} = {kiloseconds}") from geometry.circles import Circle from geometry.rectangles import Rectangle, Square cameraSensor = Rectangle(720, 720) circumscribed = Circle.circumscribe(cameraSensor.length, cameraSensor.height) print(